Skip to main content

synth_backend/
arm_encoder.rs

1//! ARM Code Encoder - Converts ARM instructions to binary machine code
2//!
3//! Generates ARM32/Thumb-2 machine code from ARM instruction structures
4
5use synth_core::Result;
6use synth_core::target::FPUPrecision;
7use synth_synthesis::contracts::encoding as encoding_contracts;
8use synth_synthesis::{ArmOp, MemAddr, MveSize, Operand2, QReg, Reg, VfpReg};
9
10/// ARM instruction encoding
11pub struct ArmEncoder {
12    /// Use Thumb mode (vs ARM mode)
13    thumb_mode: bool,
14    /// FPU capability for VFP instruction encoding
15    #[allow(dead_code)]
16    fpu: Option<FPUPrecision>,
17}
18
19impl ArmEncoder {
20    /// Create a new ARM encoder in ARM32 mode
21    pub fn new_arm32() -> Self {
22        Self {
23            thumb_mode: false,
24            fpu: None,
25        }
26    }
27
28    /// Create a new ARM encoder in Thumb-2 mode
29    pub fn new_thumb2() -> Self {
30        Self {
31            thumb_mode: true,
32            fpu: None,
33        }
34    }
35
36    /// Create a new Thumb-2 encoder with FPU capability
37    pub fn new_thumb2_with_fpu(fpu: Option<FPUPrecision>) -> Self {
38        Self {
39            thumb_mode: true,
40            fpu,
41        }
42    }
43
44    /// Encode a single ARM instruction to bytes
45    pub fn encode(&self, op: &ArmOp) -> Result<Vec<u8>> {
46        if self.thumb_mode {
47            self.encode_thumb(op)
48        } else {
49            self.encode_arm(op)
50        }
51    }
52
53    /// Encode an ARM instruction in ARM32 mode (32-bit instructions)
54    /// #206: encode an ARM32 (A32) load/store whose address uses a register
55    /// offset (`[rn, rm{, #off}]`). Returns `None` for ops with no register
56    /// offset (the caller falls through to the immediate-form arms). Computes
57    /// `ip = base + rm` then re-encodes the op against `[ip, #off]`, which works
58    /// uniformly for word/byte/halfword/signed forms. IP (R12) is the scratch
59    /// register the selector already treats as clobberable across memory ops.
60    fn encode_arm_reg_offset_mem(&self, op: &ArmOp) -> Result<Option<Vec<u8>>> {
61        use synth_synthesis::Reg;
62        let addr = match op {
63            ArmOp::Ldr { addr, .. }
64            | ArmOp::Str { addr, .. }
65            | ArmOp::Ldrb { addr, .. }
66            | ArmOp::Strb { addr, .. }
67            | ArmOp::Ldrh { addr, .. }
68            | ArmOp::Strh { addr, .. }
69            | ArmOp::Ldrsb { addr, .. }
70            | ArmOp::Ldrsh { addr, .. } => addr,
71            _ => return Ok(None),
72        };
73        let Some(rm) = addr.offset_reg else {
74            return Ok(None);
75        };
76        let ip = Reg::R12;
77        // ADD ip, base, rm  (cond=AL, opcode=ADD, S=0, register operand2)
78        let add: u32 = 0xE0800000
79            | (reg_to_bits(&addr.base) << 16)
80            | (reg_to_bits(&ip) << 12)
81            | reg_to_bits(&rm);
82        let mut bytes = add.to_le_bytes().to_vec();
83        // Re-encode the op against [ip, #off] (immediate form → no offset_reg,
84        // so this recursion hits the immediate arms, not this helper again).
85        let imm_addr = MemAddr::imm(ip, addr.offset);
86        let imm_op = match op {
87            ArmOp::Ldr { rd, .. } => ArmOp::Ldr {
88                rd: *rd,
89                addr: imm_addr,
90            },
91            ArmOp::Str { rd, .. } => ArmOp::Str {
92                rd: *rd,
93                addr: imm_addr,
94            },
95            ArmOp::Ldrb { rd, .. } => ArmOp::Ldrb {
96                rd: *rd,
97                addr: imm_addr,
98            },
99            ArmOp::Strb { rd, .. } => ArmOp::Strb {
100                rd: *rd,
101                addr: imm_addr,
102            },
103            ArmOp::Ldrh { rd, .. } => ArmOp::Ldrh {
104                rd: *rd,
105                addr: imm_addr,
106            },
107            ArmOp::Strh { rd, .. } => ArmOp::Strh {
108                rd: *rd,
109                addr: imm_addr,
110            },
111            ArmOp::Ldrsb { rd, .. } => ArmOp::Ldrsb {
112                rd: *rd,
113                addr: imm_addr,
114            },
115            ArmOp::Ldrsh { rd, .. } => ArmOp::Ldrsh {
116                rd: *rd,
117                addr: imm_addr,
118            },
119            _ => unreachable!(),
120        };
121        bytes.extend(self.encode_arm(&imm_op)?);
122        Ok(Some(bytes))
123    }
124
125    fn encode_arm(&self, op: &ArmOp) -> Result<Vec<u8>> {
126        // #206: ARM32 register-offset loads/stores. `encode_mem_addr` only
127        // returns the 12-bit immediate, so the immediate-form arms below
128        // silently DROP `addr.offset_reg` — a runtime address index vanished,
129        // turning `ldr rd,[rn,rm,#off]` into `ldr rd,[rn,#off]` (the access went
130        // to the wrong address). Compute the effective base into IP and re-encode
131        // against `[ip, #off]`, which is uniform for word/byte/halfword/signed.
132        if let Some(bytes) = self.encode_arm_reg_offset_mem(op)? {
133            return Ok(bytes);
134        }
135        let instr: u32 = match op {
136            // Data processing instructions
137            ArmOp::Add { rd, rn, op2 } => {
138                let rd_bits = reg_to_bits(rd);
139                let rn_bits = reg_to_bits(rn);
140                let (op2_bits, i_flag) = encode_operand2(op2);
141
142                // ADD encoding: cond(4) | 00 | I(1) | 0100 | S(1) | Rn(4) | Rd(4) | operand2(12)
143                0xE0800000 // condition=always(E), opcode=ADD(0100), S=0
144                    | (i_flag << 25)
145                    | (rn_bits << 16)
146                    | (rd_bits << 12)
147                    | op2_bits
148            }
149
150            ArmOp::Sub { rd, rn, op2 } => {
151                let rd_bits = reg_to_bits(rd);
152                let rn_bits = reg_to_bits(rn);
153                let (op2_bits, i_flag) = encode_operand2(op2);
154
155                // SUB encoding: opcode=0010
156                0xE0400000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
157            }
158
159            // i64 support: ADDS, ADC, SUBS, SBC for ARM32
160            ArmOp::Adds { rd, rn, op2 } => {
161                let rd_bits = reg_to_bits(rd);
162                let rn_bits = reg_to_bits(rn);
163                let (op2_bits, i_flag) = encode_operand2(op2);
164
165                // ADDS encoding: opcode=0100, S=1
166                0xE0900000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
167            }
168
169            ArmOp::Adc { rd, rn, op2 } => {
170                let rd_bits = reg_to_bits(rd);
171                let rn_bits = reg_to_bits(rn);
172                let (op2_bits, i_flag) = encode_operand2(op2);
173
174                // ADC encoding: opcode=0101
175                0xE0A00000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
176            }
177
178            ArmOp::Subs { rd, rn, op2 } => {
179                let rd_bits = reg_to_bits(rd);
180                let rn_bits = reg_to_bits(rn);
181                let (op2_bits, i_flag) = encode_operand2(op2);
182
183                // SUBS encoding: opcode=0010, S=1
184                0xE0500000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
185            }
186
187            ArmOp::Sbc { rd, rn, op2 } => {
188                let rd_bits = reg_to_bits(rd);
189                let rn_bits = reg_to_bits(rn);
190                let (op2_bits, i_flag) = encode_operand2(op2);
191
192                // SBC encoding: opcode=0110
193                0xE0C00000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
194            }
195
196            ArmOp::Mul { rd, rn, rm } => {
197                let rd_bits = reg_to_bits(rd);
198                let rn_bits = reg_to_bits(rn);
199                let rm_bits = reg_to_bits(rm);
200
201                // MUL encoding: cond(4) | 000000 | A(1) | S(1) | Rd(4) | Rn(4) | Rs(4) | 1001 | Rm(4)
202                0xE0000090 | (rd_bits << 16) | (rn_bits << 8) | rm_bits
203            }
204
205            ArmOp::Umull { rdlo, rdhi, rn, rm } => {
206                let rdlo_bits = reg_to_bits(rdlo);
207                let rdhi_bits = reg_to_bits(rdhi);
208                let rn_bits = reg_to_bits(rn);
209                let rm_bits = reg_to_bits(rm);
210
211                // UMULL encoding: cond(4) | 0000 1000 | RdHi(4) | RdLo(4) | Rm(4) | 1001 | Rn(4)
212                0xE0800090 | (rdhi_bits << 16) | (rdlo_bits << 12) | (rm_bits << 8) | rn_bits
213            }
214
215            ArmOp::Sdiv { rd, rn, rm } => {
216                let rd_bits = reg_to_bits(rd);
217                let rn_bits = reg_to_bits(rn);
218                let rm_bits = reg_to_bits(rm);
219
220                // SDIV encoding: cond(4) | 01110001 | Rd(4) | 1111 | Rm(4) | 0001 | Rn(4)
221                // ARMv7-M and above
222                0xE710F010 | (rd_bits << 16) | (rm_bits << 8) | rn_bits
223            }
224
225            ArmOp::Udiv { rd, rn, rm } => {
226                let rd_bits = reg_to_bits(rd);
227                let rn_bits = reg_to_bits(rn);
228                let rm_bits = reg_to_bits(rm);
229
230                // UDIV encoding: cond(4) | 01110011 | Rd(4) | 1111 | Rm(4) | 0001 | Rn(4)
231                // ARMv7-M and above
232                0xE730F010 | (rd_bits << 16) | (rm_bits << 8) | rn_bits
233            }
234
235            ArmOp::Mls { rd, rn, rm, ra } => {
236                let rd_bits = reg_to_bits(rd);
237                let rn_bits = reg_to_bits(rn);
238                let rm_bits = reg_to_bits(rm);
239                let ra_bits = reg_to_bits(ra);
240
241                // MLS encoding: cond(4) | 00000110 | Rd(4) | Ra(4) | Rm(4) | 1001 | Rn(4)
242                // Rd = Ra - (Rn * Rm)
243                0xE0600090 | (rd_bits << 16) | (ra_bits << 12) | (rm_bits << 8) | rn_bits
244            }
245
246            ArmOp::Mla { rd, rn, rm, ra } => {
247                let rd_bits = reg_to_bits(rd);
248                let rn_bits = reg_to_bits(rn);
249                let rm_bits = reg_to_bits(rm);
250                let ra_bits = reg_to_bits(ra);
251
252                // MLA encoding: cond(4) | 0000001 S | Rd(4) | Ra(4) | Rm(4) | 1001 | Rn(4)
253                // Rd = Ra + (Rn * Rm). Base 0xE0200090 (S=0).
254                0xE0200090 | (rd_bits << 16) | (ra_bits << 12) | (rm_bits << 8) | rn_bits
255            }
256
257            ArmOp::And { rd, rn, op2 } => {
258                let rd_bits = reg_to_bits(rd);
259                let rn_bits = reg_to_bits(rn);
260                let (op2_bits, i_flag) = encode_operand2(op2);
261
262                // AND encoding: opcode=0000
263                0xE0000000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
264            }
265
266            ArmOp::Orr { rd, rn, op2 } => {
267                let rd_bits = reg_to_bits(rd);
268                let rn_bits = reg_to_bits(rn);
269                let (op2_bits, i_flag) = encode_operand2(op2);
270
271                // ORR encoding: opcode=1100
272                0xE1800000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
273            }
274
275            ArmOp::Eor { rd, rn, op2 } => {
276                let rd_bits = reg_to_bits(rd);
277                let rn_bits = reg_to_bits(rn);
278                let (op2_bits, i_flag) = encode_operand2(op2);
279
280                // EOR encoding: opcode=0001
281                0xE0200000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
282            }
283
284            // Shift instructions
285            ArmOp::Lsl { rd, rn, shift } => {
286                let rd_bits = reg_to_bits(rd);
287                let rn_bits = reg_to_bits(rn);
288                let shift_bits = *shift & 0x1F;
289
290                // LSL encoding: MOV with shift
291                0xE1A00000 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
292            }
293
294            ArmOp::Lsr { rd, rn, shift } => {
295                let rd_bits = reg_to_bits(rd);
296                let rn_bits = reg_to_bits(rn);
297                let shift_bits = *shift & 0x1F;
298
299                // LSR encoding
300                0xE1A00020 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
301            }
302
303            ArmOp::Asr { rd, rn, shift } => {
304                let rd_bits = reg_to_bits(rd);
305                let rn_bits = reg_to_bits(rn);
306                let shift_bits = *shift & 0x1F;
307
308                // ASR encoding
309                0xE1A00040 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
310            }
311
312            ArmOp::Ror { rd, rn, shift } => {
313                let rd_bits = reg_to_bits(rd);
314                let rn_bits = reg_to_bits(rn);
315                let shift_bits = *shift & 0x1F;
316
317                // ROR encoding: MOV with ROR shift
318                0xE1A00060 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
319            }
320
321            // Register-based shifts (ARM32)
322            // LSL Rd, Rn, Rm: cond 0001101S 0000 Rd Rs 0001 Rn
323            ArmOp::LslReg { rd, rn, rm } => {
324                let rd_bits = reg_to_bits(rd);
325                let rn_bits = reg_to_bits(rn);
326                let rm_bits = reg_to_bits(rm);
327                0xE1A00010 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
328            }
329            ArmOp::LsrReg { rd, rn, rm } => {
330                let rd_bits = reg_to_bits(rd);
331                let rn_bits = reg_to_bits(rn);
332                let rm_bits = reg_to_bits(rm);
333                0xE1A00030 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
334            }
335            ArmOp::AsrReg { rd, rn, rm } => {
336                let rd_bits = reg_to_bits(rd);
337                let rn_bits = reg_to_bits(rn);
338                let rm_bits = reg_to_bits(rm);
339                0xE1A00050 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
340            }
341            ArmOp::RorReg { rd, rn, rm } => {
342                let rd_bits = reg_to_bits(rd);
343                let rn_bits = reg_to_bits(rn);
344                let rm_bits = reg_to_bits(rm);
345                0xE1A00070 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
346            }
347
348            // RSB (Reverse Subtract): Rd = imm - Rn
349            ArmOp::Rsb { rd, rn, imm } => {
350                let rd_bits = reg_to_bits(rd);
351                let rn_bits = reg_to_bits(rn);
352                // RSB encoding: cond(4) | 00 1 0011 S | Rn(4) | Rd(4) | imm12
353                // Opcode for RSB = 0011, I=1 (immediate), S=0
354                0xE2600000 | (rn_bits << 16) | (rd_bits << 12) | (*imm & 0xFF)
355            }
356
357            // Bit manipulation instructions
358            ArmOp::Clz { rd, rm } => {
359                let rd_bits = reg_to_bits(rd);
360                let rm_bits = reg_to_bits(rm);
361
362                // CLZ encoding: cond(4) | 00010110 | 1111 | Rd(4) | 1111 | 0001 | Rm(4)
363                // ARMv5T and above
364                0xE16F0F10 | (rd_bits << 12) | rm_bits
365            }
366
367            ArmOp::Rbit { rd, rm } => {
368                let rd_bits = reg_to_bits(rd);
369                let rm_bits = reg_to_bits(rm);
370
371                // RBIT encoding: cond(4) | 01101111 | 1111 | Rd(4) | 1111 | 0011 | Rm(4)
372                // ARMv6T2 and above
373                0xE6FF0F30 | (rd_bits << 12) | rm_bits
374            }
375
376            ArmOp::Sxtb { rd, rm } => {
377                let rd_bits = reg_to_bits(rd);
378                let rm_bits = reg_to_bits(rm);
379
380                // SXTB encoding: cond(4) | 01101010 | 1111 | Rd(4) | rotate(2) | 00 | 0111 | Rm(4)
381                // ARMv6 and above. rotate=00 for no rotation
382                0xE6AF0070 | (rd_bits << 12) | rm_bits
383            }
384
385            ArmOp::Sxth { rd, rm } => {
386                let rd_bits = reg_to_bits(rd);
387                let rm_bits = reg_to_bits(rm);
388
389                // SXTH encoding: cond(4) | 01101011 | 1111 | Rd(4) | rotate(2) | 00 | 0111 | Rm(4)
390                // ARMv6 and above. rotate=00 for no rotation
391                0xE6BF0070 | (rd_bits << 12) | rm_bits
392            }
393
394            // Move instructions
395            ArmOp::Mov { rd, op2 } => {
396                let rd_bits = reg_to_bits(rd);
397                let (op2_bits, i_flag) = encode_operand2(op2);
398
399                // MOV encoding: opcode=1101
400                0xE1A00000 | (i_flag << 25) | (rd_bits << 12) | op2_bits
401            }
402
403            ArmOp::Mvn { rd, op2 } => {
404                let rd_bits = reg_to_bits(rd);
405                let (op2_bits, i_flag) = encode_operand2(op2);
406
407                // MVN encoding: opcode=1111
408                0xE1E00000 | (i_flag << 25) | (rd_bits << 12) | op2_bits
409            }
410
411            // MOVW - Move Wide (ARM32)
412            // Encoding: cond(4) | 0011 0000 | imm4(4) | Rd(4) | imm12(12)
413            ArmOp::Movw { rd, imm16 } => {
414                let rd_bits = reg_to_bits(rd);
415                let imm4 = ((*imm16 as u32) >> 12) & 0xF;
416                let imm12 = (*imm16 as u32) & 0xFFF;
417                0xE3000000 | (imm4 << 16) | (rd_bits << 12) | imm12
418            }
419
420            // MOVT - Move Top (ARM32)
421            // Encoding: cond(4) | 0011 0100 | imm4(4) | Rd(4) | imm12(12)
422            ArmOp::Movt { rd, imm16 } => {
423                let rd_bits = reg_to_bits(rd);
424                let imm4 = ((*imm16 as u32) >> 12) & 0xF;
425                let imm12 = (*imm16 as u32) & 0xFFF;
426                0xE3400000 | (imm4 << 16) | (rd_bits << 12) | imm12
427            }
428
429            // #237: symbol-relative MOVW/MOVT (ARM mode) — addend in place, the
430            // backend records the MOVW_ABS/MOVT_ABS relocation against `symbol`.
431            ArmOp::MovwSym { rd, addend, .. } => {
432                let rd_bits = reg_to_bits(rd);
433                let v = (*addend as u32) & 0xffff;
434                0xE3000000 | (((v >> 12) & 0xF) << 16) | (rd_bits << 12) | (v & 0xFFF)
435            }
436            ArmOp::MovtSym { rd, addend, .. } => {
437                let rd_bits = reg_to_bits(rd);
438                let v = ((*addend as u32) >> 16) & 0xffff;
439                0xE3400000 | (((v >> 12) & 0xF) << 16) | (rd_bits << 12) | (v & 0xFFF)
440            }
441
442            // Compare
443            ArmOp::Cmp { rn, op2 } => {
444                let rn_bits = reg_to_bits(rn);
445                let (op2_bits, i_flag) = encode_operand2(op2);
446
447                // CMP encoding: opcode=1010, S=1
448                0xE1500000 | (i_flag << 25) | (rn_bits << 16) | op2_bits
449            }
450
451            // Compare Negative (CMN) - computes Rn + op2 and sets flags
452            ArmOp::Cmn { rn, op2 } => {
453                let rn_bits = reg_to_bits(rn);
454                let (op2_bits, i_flag) = encode_operand2(op2);
455
456                // CMN encoding: opcode=1011, S=1
457                0xE1700000 | (i_flag << 25) | (rn_bits << 16) | op2_bits
458            }
459
460            // Load/Store
461            ArmOp::Ldr { rd, addr } => {
462                let rd_bits = reg_to_bits(rd);
463                let (base_bits, offset_bits) = encode_mem_addr(addr);
464
465                // LDR encoding: cond(4) | 01 | I(1) | P(1) | U(1) | B(1) | W(1) | L(1) | Rn(4) | Rd(4) | offset(12)
466                // P=1 (pre-indexed), U=1 (add offset), L=1 (load)
467                0xE5900000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
468            }
469
470            ArmOp::Str { rd, addr } => {
471                let rd_bits = reg_to_bits(rd);
472                let (base_bits, offset_bits) = encode_mem_addr(addr);
473
474                // STR encoding: L=0 (store)
475                0xE5800000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
476            }
477
478            // Sub-word loads (ARM32 encoding)
479            ArmOp::Ldrb { rd, addr } => {
480                let rd_bits = reg_to_bits(rd);
481                let (base_bits, offset_bits) = encode_mem_addr(addr);
482                // LDRB: LDR with B=1 (byte): cond|01|I|P|U|1|W|L|Rn|Rd|offset
483                0xE5D00000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
484            }
485
486            ArmOp::Ldrsb { rd, addr } => {
487                let rd_bits = reg_to_bits(rd);
488                let (base_bits, offset_bits) = encode_mem_addr(addr);
489                // LDRSB (misc load): cond|000|P|U|1|W|1|Rn|Rd|imm4H|1101|imm4L
490                // Simplified with immediate offset
491                let offset_val = offset_bits & 0xFF;
492                let imm4h = (offset_val >> 4) & 0xF;
493                let imm4l = offset_val & 0xF;
494                0xE1D000D0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
495            }
496
497            ArmOp::Ldrh { rd, addr } => {
498                let rd_bits = reg_to_bits(rd);
499                let (base_bits, offset_bits) = encode_mem_addr(addr);
500                // LDRH (misc load): cond|000|P|U|1|W|1|Rn|Rd|imm4H|1011|imm4L
501                let offset_val = offset_bits & 0xFF;
502                let imm4h = (offset_val >> 4) & 0xF;
503                let imm4l = offset_val & 0xF;
504                0xE1D000B0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
505            }
506
507            ArmOp::Ldrsh { rd, addr } => {
508                let rd_bits = reg_to_bits(rd);
509                let (base_bits, offset_bits) = encode_mem_addr(addr);
510                // LDRSH (misc load): cond|000|P|U|1|W|1|Rn|Rd|imm4H|1111|imm4L
511                let offset_val = offset_bits & 0xFF;
512                let imm4h = (offset_val >> 4) & 0xF;
513                let imm4l = offset_val & 0xF;
514                0xE1D000F0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
515            }
516
517            // Sub-word stores (ARM32 encoding)
518            ArmOp::Strb { rd, addr } => {
519                let rd_bits = reg_to_bits(rd);
520                let (base_bits, offset_bits) = encode_mem_addr(addr);
521                // STRB: STR with B=1 (byte): cond|01|I|P|U|1|W|0|Rn|Rd|offset
522                0xE5C00000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
523            }
524
525            ArmOp::Strh { rd, addr } => {
526                let rd_bits = reg_to_bits(rd);
527                let (base_bits, offset_bits) = encode_mem_addr(addr);
528                // STRH (misc store): cond|000|P|U|1|W|0|Rn|Rd|imm4H|1011|imm4L
529                let offset_val = offset_bits & 0xFF;
530                let imm4h = (offset_val >> 4) & 0xF;
531                let imm4l = offset_val & 0xF;
532                0xE1C000B0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
533            }
534
535            // Memory management (ARM32 encoding)
536            ArmOp::MemorySize { rd } => {
537                let rd_bits = reg_to_bits(rd);
538                // MOV rd, R10, LSR #16  (memory size in bytes / 65536 = pages)
539                // cond|000|1101|S|0000|Rd|shift5|type|0|Rm
540                // LSR #16: shift5=10000, type=01
541                0xE1A00820 | (rd_bits << 12) | 0x0A // Rm=R10, shift=16, LSR
542            }
543
544            ArmOp::MemoryGrow { rd, .. } => {
545                let rd_bits = reg_to_bits(rd);
546                // On embedded, always fail: MOV rd, #-1
547                0xE3E00000 | (rd_bits << 12) // MVN rd, #0 = MOV rd, #-1
548            }
549
550            // Label pseudo-instruction: emits no machine code
551            ArmOp::Label { .. } => {
552                return Ok(Vec::new());
553            }
554
555            // Branch instructions
556            ArmOp::B { label: _ } => {
557                // B encoding: cond(4) | 1010 | offset(24)
558                // Simplified: branch to offset 0 (will be patched by linker/resolver)
559                0xEA000000
560            }
561
562            // Conditional branch to label (generic)
563            ArmOp::Bcc { cond, label: _ } => {
564                use synth_synthesis::Condition;
565                let cond_bits: u32 = match cond {
566                    Condition::EQ => 0x0,
567                    Condition::NE => 0x1,
568                    Condition::HS => 0x2,
569                    Condition::LO => 0x3,
570                    Condition::HI => 0x8,
571                    Condition::LS => 0x9,
572                    Condition::GE => 0xA,
573                    Condition::LT => 0xB,
574                    Condition::GT => 0xC,
575                    Condition::LE => 0xD,
576                };
577                // B<cond> with offset 0 (will be patched)
578                (cond_bits << 28) | 0x0A000000
579            }
580
581            // BHS (Branch if Higher or Same) - used for bounds checking
582            ArmOp::Bhs { label: _ } => {
583                // BHS encoding: cond(2=HS) | 1010 | offset(24)
584                0x2A000000 // BHS with offset 0
585            }
586
587            // BLO (Branch if Lower) - complementary to BHS
588            ArmOp::Blo { label: _ } => {
589                // BLO encoding: cond(3=LO) | 1010 | offset(24)
590                0x3A000000 // BLO with offset 0
591            }
592
593            // Branch with numeric offset (in instructions)
594            // ARM32 B instruction: offset is in instructions, stored as words
595            // The offset is relative to PC+8 (due to ARM pipeline)
596            ArmOp::BOffset { offset } => {
597                // B encoding: cond(4) | 1010 | offset(24)
598                // Offset is signed, in words (4-byte units)
599                // ARM adds PC+8 to the offset, so we need to adjust:
600                // target = PC + 8 + (offset * 4)
601                // For backward branch of N instructions: offset = -(N + 2)
602                // wrapping_sub keeps the encoder total under fuzzing (#186): an
603                // extreme i32::MIN offset would otherwise overflow-panic; for any
604                // real branch offset this is identical to `- 2`.
605                let adjusted_offset = offset.wrapping_sub(2); // Account for PC+8
606                let offset_bits = (adjusted_offset as u32) & 0x00FFFFFF;
607                0xEA000000 | offset_bits
608            }
609
610            // Conditional branch with numeric offset
611            ArmOp::BCondOffset { cond, offset } => {
612                use synth_synthesis::Condition;
613                let cond_bits: u32 = match cond {
614                    Condition::EQ => 0x0,
615                    Condition::NE => 0x1,
616                    Condition::HS => 0x2,
617                    Condition::LO => 0x3,
618                    Condition::HI => 0x8,
619                    Condition::LS => 0x9,
620                    Condition::GE => 0xA,
621                    Condition::LT => 0xB,
622                    Condition::GT => 0xC,
623                    Condition::LE => 0xD,
624                };
625                // B<cond> encoding: cond(4) | 1010 | offset(24)
626                // wrapping_sub: total under fuzzing (#186), identical for real offsets.
627                let adjusted_offset = offset.wrapping_sub(2); // Account for PC+8
628                let offset_bits = (adjusted_offset as u32) & 0x00FFFFFF;
629                (cond_bits << 28) | 0x0A000000 | offset_bits
630            }
631
632            ArmOp::Bl { label: _ } => {
633                // BL encoding: cond(4) | 1011 | offset(24)
634                0xEB000000
635            }
636
637            ArmOp::Bx { rm } => {
638                let rm_bits = reg_to_bits(rm);
639
640                // BX encoding: cond(4) | 000100101111111111110001 | Rm(4)
641                0xE12FFF10 | rm_bits
642            }
643
644            ArmOp::Blx { rm } => {
645                let rm_bits = reg_to_bits(rm);
646
647                // BLX (register) encoding: cond(4) | 000100101111111111110011 | Rm(4)
648                0xE12FFF30 | rm_bits
649            }
650
651            ArmOp::Push { regs } => {
652                // STMDB SP!, {regs} encoding: cond(4) | 100100 | 10 | 1101 | register_list(16)
653                let mut reg_list: u32 = 0;
654                for r in regs {
655                    reg_list |= 1 << reg_to_bits(r);
656                }
657                0xE92D0000 | reg_list
658            }
659
660            ArmOp::Pop { regs } => {
661                // LDMIA SP!, {regs} encoding: cond(4) | 100010 | 11 | 1101 | register_list(16)
662                let mut reg_list: u32 = 0;
663                for r in regs {
664                    reg_list |= 1 << reg_to_bits(r);
665                }
666                0xE8BD0000 | reg_list
667            }
668
669            ArmOp::Nop => {
670                // NOP encoding: MOV R0, R0
671                0xE1A00000
672            }
673
674            ArmOp::Udf { imm } => {
675                // UDF (Undefined) encoding in ARM: 0xE7F000F0 | (imm12_hi << 8) | imm4_lo
676                // We only use imm8, so split into imm4_hi and imm4_lo
677                let imm8 = *imm as u32;
678                0xE7F000F0 | ((imm8 & 0xF0) << 4) | (imm8 & 0x0F)
679            }
680
681            // Pseudo-instructions for verification - encode as NOP
682            // These are used in formal verification but not actual code generation
683            ArmOp::Popcnt { .. } => {
684                // Population count pseudo-instruction
685                // Not a real ARM instruction, would be expanded to actual code
686                0xE1A00000 // NOP for now
687            }
688
689            ArmOp::SetCond { .. } => {
690                // Condition evaluation pseudo-instruction
691                // Not a real ARM instruction, would be expanded to actual code
692                0xE1A00000 // NOP for now
693            }
694
695            ArmOp::SelectMove { .. } => {
696                // Conditional move pseudo-instruction for ARM32
697                // Would use MOV{cond} instruction
698                0xE1A00000 // NOP for now
699            }
700
701            ArmOp::Select { .. } => {
702                // Select pseudo-instruction
703                // Not a real ARM instruction, would be expanded to conditional moves
704                0xE1A00000 // NOP for now
705            }
706
707            ArmOp::LocalGet { .. } => {
708                // Local variable get pseudo-instruction
709                // Not a real ARM instruction, would be expanded to memory access
710                0xE1A00000 // NOP for now
711            }
712
713            ArmOp::LocalSet { .. } => {
714                // Local variable set pseudo-instruction
715                // Not a real ARM instruction, would be expanded to memory access
716                0xE1A00000 // NOP for now
717            }
718
719            ArmOp::LocalTee { .. } => {
720                // Local variable tee pseudo-instruction
721                // Not a real ARM instruction, would be expanded to memory access
722                0xE1A00000 // NOP for now
723            }
724
725            ArmOp::GlobalGet { .. } => {
726                // Global variable get pseudo-instruction
727                // Not a real ARM instruction, would be expanded to memory access
728                0xE1A00000 // NOP for now
729            }
730
731            ArmOp::GlobalSet { .. } => {
732                // Global variable set pseudo-instruction
733                // Not a real ARM instruction, would be expanded to memory access
734                0xE1A00000 // NOP for now
735            }
736
737            ArmOp::BrTable { .. } => {
738                // Branch table pseudo-instruction
739                // Not a real ARM instruction, would be expanded to jump table
740                0xE1A00000 // NOP for now
741            }
742
743            ArmOp::Call { .. } => {
744                // Function call pseudo-instruction
745                // Not a real ARM instruction, would be expanded to BL
746                0xE1A00000 // NOP for now
747            }
748
749            ArmOp::CallIndirect { .. } => {
750                // Indirect function call pseudo-instruction
751                // Not a real ARM instruction, would be expanded to indirect branch
752                0xE1A00000 // NOP for now
753            }
754
755            // i64 pseudo-instructions (Phase 2) - encode as NOP for now
756            // Real compiler would expand these to multi-instruction sequences
757            ArmOp::I64Add { .. } => 0xE1A00000,        // NOP
758            ArmOp::I64Sub { .. } => 0xE1A00000,        // NOP
759            ArmOp::I64DivS { .. } => 0xE1A00000,       // NOP
760            ArmOp::I64DivU { .. } => 0xE1A00000,       // NOP
761            ArmOp::I64RemS { .. } => 0xE1A00000,       // NOP
762            ArmOp::I64RemU { .. } => 0xE1A00000,       // NOP
763            ArmOp::I64Clz { .. } => 0xE1A00000,        // NOP
764            ArmOp::I64Ctz { .. } => 0xE1A00000,        // NOP
765            ArmOp::I64Popcnt { .. } => 0xE1A00000,     // NOP
766            ArmOp::I64And { .. } => 0xE1A00000,        // NOP
767            ArmOp::I64Or { .. } => 0xE1A00000,         // NOP
768            ArmOp::I64Xor { .. } => 0xE1A00000,        // NOP
769            ArmOp::I64Eqz { .. } => 0xE1A00000,        // NOP
770            ArmOp::I64Eq { .. } => 0xE1A00000,         // NOP
771            ArmOp::I64Ne { .. } => 0xE1A00000,         // NOP
772            ArmOp::I64LtS { .. } => 0xE1A00000,        // NOP
773            ArmOp::I64LtU { .. } => 0xE1A00000,        // NOP
774            ArmOp::I64LeS { .. } => 0xE1A00000,        // NOP
775            ArmOp::I64LeU { .. } => 0xE1A00000,        // NOP
776            ArmOp::I64GtS { .. } => 0xE1A00000,        // NOP
777            ArmOp::I64GtU { .. } => 0xE1A00000,        // NOP
778            ArmOp::I64GeS { .. } => 0xE1A00000,        // NOP
779            ArmOp::I64GeU { .. } => 0xE1A00000,        // NOP
780            ArmOp::I64Const { .. } => 0xE1A00000,      // NOP
781            ArmOp::I64Ldr { .. } => 0xE1A00000,        // NOP
782            ArmOp::I64Str { .. } => 0xE1A00000,        // NOP
783            ArmOp::I64ExtendI32S { .. } => 0xE1A00000, // NOP
784            ArmOp::I64ExtendI32U { .. } => 0xE1A00000, // NOP
785            ArmOp::I64Extend8S { .. } => 0xE1A00000,   // NOP (Thumb-2 only)
786            ArmOp::I64Extend16S { .. } => 0xE1A00000,  // NOP (Thumb-2 only)
787            ArmOp::I64Extend32S { .. } => 0xE1A00000,  // NOP (Thumb-2 only)
788            ArmOp::I32WrapI64 { .. } => 0xE1A00000,    // NOP
789
790            // f32 VFP single-precision instructions
791            ArmOp::F32Add { sd, sn, sm } => encode_vfp_3reg(0xEE300A00, sd, sn, sm)?,
792            ArmOp::F32Sub { sd, sn, sm } => encode_vfp_3reg(0xEE300A40, sd, sn, sm)?,
793            ArmOp::F32Mul { sd, sn, sm } => encode_vfp_3reg(0xEE200A00, sd, sn, sm)?,
794            ArmOp::F32Div { sd, sn, sm } => encode_vfp_3reg(0xEE800A00, sd, sn, sm)?,
795            ArmOp::F32Abs { sd, sm } => encode_vfp_2reg(0xEEB00AC0, sd, sm)?,
796            ArmOp::F32Neg { sd, sm } => encode_vfp_2reg(0xEEB10A40, sd, sm)?,
797            ArmOp::F32Sqrt { sd, sm } => encode_vfp_2reg(0xEEB10AC0, sd, sm)?,
798
799            // f32 pseudo-ops — multi-instruction sequences
800            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
801            ArmOp::F32Ceil { sd, sm } => {
802                return self.encode_arm_f32_rounding(sd, sm, 0b01); // Round toward +Inf
803            }
804            ArmOp::F32Floor { sd, sm } => {
805                return self.encode_arm_f32_rounding(sd, sm, 0b10); // Round toward -Inf
806            }
807            ArmOp::F32Trunc { sd, sm } => {
808                return self.encode_arm_f32_rounding(sd, sm, 0b11); // VCVT toward zero
809            }
810            ArmOp::F32Nearest { sd, sm } => {
811                return self.encode_arm_f32_rounding(sd, sm, 0b00); // VCVT to nearest
812            }
813            ArmOp::F32Min { sd, sn, sm } => {
814                return self.encode_arm_f32_minmax(sd, sn, sm, true);
815            }
816            ArmOp::F32Max { sd, sn, sm } => {
817                return self.encode_arm_f32_minmax(sd, sn, sm, false);
818            }
819            ArmOp::F32Copysign { sd, sn, sm } => {
820                return self.encode_arm_f32_copysign(sd, sn, sm);
821            }
822
823            // f32 comparisons — multi-instruction: VCMP + VMRS + conditional MOV
824            ArmOp::F32Eq { rd, sn, sm } => {
825                return self.encode_arm_f32_compare(rd, sn, sm, 0x0); // EQ
826            }
827            ArmOp::F32Ne { rd, sn, sm } => {
828                return self.encode_arm_f32_compare(rd, sn, sm, 0x1); // NE
829            }
830            ArmOp::F32Lt { rd, sn, sm } => {
831                return self.encode_arm_f32_compare(rd, sn, sm, 0x4); // MI (less than)
832            }
833            ArmOp::F32Le { rd, sn, sm } => {
834                return self.encode_arm_f32_compare(rd, sn, sm, 0x9); // LS (less or same)
835            }
836            ArmOp::F32Gt { rd, sn, sm } => {
837                return self.encode_arm_f32_compare(rd, sn, sm, 0xC); // GT
838            }
839            ArmOp::F32Ge { rd, sn, sm } => {
840                return self.encode_arm_f32_compare(rd, sn, sm, 0xA); // GE
841            }
842
843            // f32 const — multi-instruction: MOVW + MOVT + VMOV
844            ArmOp::F32Const { sd, value } => {
845                return self.encode_arm_f32_const(sd, *value);
846            }
847
848            ArmOp::F32Load { sd, addr } => encode_vfp_ldst(0xED900A00, sd, addr)?,
849            ArmOp::F32Store { sd, addr } => encode_vfp_ldst(0xED800A00, sd, addr)?,
850
851            // f32 conversions — multi-instruction sequences
852            ArmOp::F32ConvertI32S { sd, rm } => {
853                return self.encode_arm_f32_convert_i32(sd, rm, true);
854            }
855            ArmOp::F32ConvertI32U { sd, rm } => {
856                return self.encode_arm_f32_convert_i32(sd, rm, false);
857            }
858            ArmOp::F32ConvertI64S { .. } | ArmOp::F32ConvertI64U { .. } => {
859                return Err(synth_core::Error::synthesis(
860                    "F32 i64 conversion not supported (requires register pairs on 32-bit ARM)",
861                ));
862            }
863            ArmOp::F32ReinterpretI32 { sd, rm } => encode_vmov_core_sreg(true, sd, rm)?,
864            ArmOp::I32ReinterpretF32 { rd, sm } => encode_vmov_core_sreg(false, sm, rd)?,
865            ArmOp::I32TruncF32S { rd, sm } => {
866                return self.encode_arm_i32_trunc_f32(rd, sm, true);
867            }
868            ArmOp::I32TruncF32U { rd, sm } => {
869                return self.encode_arm_i32_trunc_f32(rd, sm, false);
870            }
871
872            // f64 VFP double-precision instructions (ARM32)
873            // F64 arithmetic: same as F32 but with sz=1 (bit 8 = 1, cp11 = 0xB)
874            ArmOp::F64Add { dd, dn, dm } => encode_vfp_3reg_f64(0xEE300B00, dd, dn, dm)?,
875            ArmOp::F64Sub { dd, dn, dm } => encode_vfp_3reg_f64(0xEE300B40, dd, dn, dm)?,
876            ArmOp::F64Mul { dd, dn, dm } => encode_vfp_3reg_f64(0xEE200B00, dd, dn, dm)?,
877            ArmOp::F64Div { dd, dn, dm } => encode_vfp_3reg_f64(0xEE800B00, dd, dn, dm)?,
878            ArmOp::F64Abs { dd, dm } => encode_vfp_2reg_f64(0xEEB00BC0, dd, dm)?,
879            ArmOp::F64Neg { dd, dm } => encode_vfp_2reg_f64(0xEEB10B40, dd, dm)?,
880            ArmOp::F64Sqrt { dd, dm } => encode_vfp_2reg_f64(0xEEB10BC0, dd, dm)?,
881
882            // f64 pseudo-ops
883            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
884            ArmOp::F64Ceil { dd, dm } => {
885                return self.encode_arm_f64_rounding(dd, dm, 0b01);
886            }
887            ArmOp::F64Floor { dd, dm } => {
888                return self.encode_arm_f64_rounding(dd, dm, 0b10);
889            }
890            ArmOp::F64Trunc { dd, dm } => {
891                return self.encode_arm_f64_rounding(dd, dm, 0b11);
892            }
893            ArmOp::F64Nearest { dd, dm } => {
894                return self.encode_arm_f64_rounding(dd, dm, 0b00);
895            }
896            ArmOp::F64Min { dd, dn, dm } => {
897                return self.encode_arm_f64_minmax(dd, dn, dm, true);
898            }
899            ArmOp::F64Max { dd, dn, dm } => {
900                return self.encode_arm_f64_minmax(dd, dn, dm, false);
901            }
902            ArmOp::F64Copysign { dd, dn, dm } => {
903                return self.encode_arm_f64_copysign(dd, dn, dm);
904            }
905
906            // f64 comparisons
907            ArmOp::F64Eq { rd, dn, dm } => {
908                return self.encode_arm_f64_compare(rd, dn, dm, 0x0);
909            }
910            ArmOp::F64Ne { rd, dn, dm } => {
911                return self.encode_arm_f64_compare(rd, dn, dm, 0x1);
912            }
913            ArmOp::F64Lt { rd, dn, dm } => {
914                return self.encode_arm_f64_compare(rd, dn, dm, 0x4);
915            }
916            ArmOp::F64Le { rd, dn, dm } => {
917                return self.encode_arm_f64_compare(rd, dn, dm, 0x9);
918            }
919            ArmOp::F64Gt { rd, dn, dm } => {
920                return self.encode_arm_f64_compare(rd, dn, dm, 0xC);
921            }
922            ArmOp::F64Ge { rd, dn, dm } => {
923                return self.encode_arm_f64_compare(rd, dn, dm, 0xA);
924            }
925
926            ArmOp::F64Const { dd, value } => {
927                return self.encode_arm_f64_const(dd, *value);
928            }
929
930            ArmOp::F64Load { dd, addr } => encode_vfp_ldst_f64(0xED900B00, dd, addr)?,
931            ArmOp::F64Store { dd, addr } => encode_vfp_ldst_f64(0xED800B00, dd, addr)?,
932
933            ArmOp::F64ConvertI32S { dd, rm } => {
934                return self.encode_arm_f64_convert_i32(dd, rm, true);
935            }
936            ArmOp::F64ConvertI32U { dd, rm } => {
937                return self.encode_arm_f64_convert_i32(dd, rm, false);
938            }
939            ArmOp::F64ConvertI64S { .. } | ArmOp::F64ConvertI64U { .. } => {
940                return Err(synth_core::Error::synthesis(
941                    "F64 i64 conversion not supported (requires register pairs on 32-bit ARM)",
942                ));
943            }
944            ArmOp::F64PromoteF32 { dd, sm } => {
945                return self.encode_arm_f64_promote_f32(dd, sm);
946            }
947            ArmOp::F64ReinterpretI64 { dd, rmlo, rmhi } => {
948                encode_vmov_core_dreg(true, dd, rmlo, rmhi)?
949            }
950            ArmOp::I64ReinterpretF64 { rdlo, rdhi, dm } => {
951                encode_vmov_core_dreg(false, dm, rdlo, rdhi)?
952            }
953            ArmOp::I64TruncF64S { .. } | ArmOp::I64TruncF64U { .. } => {
954                return Err(synth_core::Error::synthesis(
955                    "i64 truncation from F64 not supported (requires i64 register pairs on 32-bit ARM)",
956                ));
957            }
958            ArmOp::I32TruncF64S { rd, dm } => {
959                return self.encode_arm_i32_trunc_f64(rd, dm, true);
960            }
961            ArmOp::I32TruncF64U { rd, dm } => {
962                return self.encode_arm_i32_trunc_f64(rd, dm, false);
963            }
964            // Multi-instruction sequences - only meaningful in Thumb-2 mode
965            ArmOp::I64SetCond { .. }
966            | ArmOp::I64SetCondZ { .. }
967            | ArmOp::I64Mul { .. }
968            | ArmOp::I64Shl { .. }
969            | ArmOp::I64ShrS { .. }
970            | ArmOp::I64ShrU { .. }
971            | ArmOp::I64Rotl { .. }
972            | ArmOp::I64Rotr { .. } => 0xE1A00000, // NOP (Thumb-2 only)
973
974            // MVE instructions — Thumb-2 only (Cortex-M55 is always Thumb-2)
975            ArmOp::MveLoad { .. }
976            | ArmOp::MveStore { .. }
977            | ArmOp::MveConst { .. }
978            | ArmOp::MveAnd { .. }
979            | ArmOp::MveOrr { .. }
980            | ArmOp::MveEor { .. }
981            | ArmOp::MveMvn { .. }
982            | ArmOp::MveBic { .. }
983            | ArmOp::MveAddI { .. }
984            | ArmOp::MveSubI { .. }
985            | ArmOp::MveMulI { .. }
986            | ArmOp::MveNegI { .. }
987            | ArmOp::MveCmpEqI { .. }
988            | ArmOp::MveCmpNeI { .. }
989            | ArmOp::MveCmpLtS { .. }
990            | ArmOp::MveCmpLtU { .. }
991            | ArmOp::MveCmpGtS { .. }
992            | ArmOp::MveCmpGtU { .. }
993            | ArmOp::MveCmpLeS { .. }
994            | ArmOp::MveCmpLeU { .. }
995            | ArmOp::MveCmpGeS { .. }
996            | ArmOp::MveCmpGeU { .. }
997            | ArmOp::MveDup { .. }
998            | ArmOp::MveExtractLane { .. }
999            | ArmOp::MveInsertLane { .. }
1000            | ArmOp::MveAddF32 { .. }
1001            | ArmOp::MveSubF32 { .. }
1002            | ArmOp::MveMulF32 { .. }
1003            | ArmOp::MveNegF32 { .. }
1004            | ArmOp::MveAbsF32 { .. }
1005            | ArmOp::MveCmpEqF32 { .. }
1006            | ArmOp::MveCmpNeF32 { .. }
1007            | ArmOp::MveCmpLtF32 { .. }
1008            | ArmOp::MveCmpLeF32 { .. }
1009            | ArmOp::MveCmpGtF32 { .. }
1010            | ArmOp::MveCmpGeF32 { .. }
1011            | ArmOp::MveDupF32 { .. }
1012            | ArmOp::MveExtractLaneF32 { .. }
1013            | ArmOp::MveReplaceLaneF32 { .. }
1014            | ArmOp::MveDivF32 { .. }
1015            | ArmOp::MveSqrtF32 { .. } => 0xE1A00000, // NOP (MVE = Thumb-2 only)
1016        };
1017
1018        // ARM32 instructions are little-endian
1019        Ok(instr.to_le_bytes().to_vec())
1020    }
1021
1022    // === ARM32 VFP multi-instruction helpers ===
1023
1024    /// Encode F32 comparison as ARM32: VCMP.F32 + VMRS + MOV rd,#0 + MOVcond rd,#1
1025    fn encode_arm_f32_compare(
1026        &self,
1027        rd: &Reg,
1028        sn: &VfpReg,
1029        sm: &VfpReg,
1030        cond_code: u32,
1031    ) -> Result<Vec<u8>> {
1032        let mut bytes = Vec::new();
1033
1034        // VCMP.F32 Sn, Sm: 0xEEB40A40 with Sn in Vd position, Sm in Vm position
1035        let sn_num = vfp_sreg_to_num(sn)?;
1036        let sm_num = vfp_sreg_to_num(sm)?;
1037        let (vd, d) = encode_sreg(sn_num);
1038        let (vm, m) = encode_sreg(sm_num);
1039        let vcmp = 0xEEB40A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1040        bytes.extend_from_slice(&vcmp.to_le_bytes());
1041
1042        // VMRS APSR_nzcv, FPSCR: 0xEEF1FA10
1043        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1044
1045        // MOV rd, #0: 0xE3A0_0000 | (rd << 12)
1046        let rd_bits = reg_to_bits(rd);
1047        let mov_zero = 0xE3A00000 | (rd_bits << 12);
1048        bytes.extend_from_slice(&mov_zero.to_le_bytes());
1049
1050        // MOVcond rd, #1: cond(4) | 0011 1010 0000 rd(4) 0000 0000 0001
1051        let mov_one = (cond_code << 28) | 0x03A00001 | (rd_bits << 12);
1052        bytes.extend_from_slice(&mov_one.to_le_bytes());
1053
1054        Ok(bytes)
1055    }
1056
1057    /// Encode F32 constant load as ARM32: MOVW Rt,#lo16 + MOVT Rt,#hi16 + VMOV Sd,Rt
1058    fn encode_arm_f32_const(&self, sd: &VfpReg, value: f32) -> Result<Vec<u8>> {
1059        let mut bytes = Vec::new();
1060        let bits = value.to_bits();
1061
1062        // Use R12 as temp register for constant loading
1063        let rt: u32 = 12; // R12/IP
1064
1065        // MOVW R12, #lo16: 0xE300_C000 | (imm4 << 16) | imm12
1066        let lo16 = bits & 0xFFFF;
1067        let movw = 0xE3000000 | (rt << 12) | ((lo16 >> 12) << 16) | (lo16 & 0xFFF);
1068        bytes.extend_from_slice(&movw.to_le_bytes());
1069
1070        // MOVT R12, #hi16: 0xE340_C000 | (imm4 << 16) | imm12
1071        let hi16 = (bits >> 16) & 0xFFFF;
1072        let movt = 0xE3400000 | (rt << 12) | ((hi16 >> 12) << 16) | (hi16 & 0xFFF);
1073        bytes.extend_from_slice(&movt.to_le_bytes());
1074
1075        // VMOV Sd, R12
1076        let vmov = encode_vmov_core_sreg(true, sd, &Reg::R12)?;
1077        bytes.extend_from_slice(&vmov.to_le_bytes());
1078
1079        Ok(bytes)
1080    }
1081
1082    /// Encode VMOV + VCVT.F32.S32/U32 as ARM32
1083    fn encode_arm_f32_convert_i32(&self, sd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
1084        let mut bytes = Vec::new();
1085
1086        // VMOV Sd, Rm — move integer to VFP register
1087        let vmov = encode_vmov_core_sreg(true, sd, rm)?;
1088        bytes.extend_from_slice(&vmov.to_le_bytes());
1089
1090        // VCVT.F32.S32 Sd, Sd (signed) or VCVT.F32.U32 Sd, Sd (unsigned)
1091        // Base: 0xEEB80A40 (signed) or 0xEEB80AC0 (unsigned)
1092        let sd_num = vfp_sreg_to_num(sd)?;
1093        let (vd, d) = encode_sreg(sd_num);
1094        let (vm, m) = encode_sreg(sd_num); // same register as source
1095        let base = if signed { 0xEEB80A40 } else { 0xEEB80AC0 };
1096        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
1097        bytes.extend_from_slice(&vcvt.to_le_bytes());
1098
1099        Ok(bytes)
1100    }
1101
1102    /// Encode F32 rounding pseudo-op as ARM32 via VCVT to integer and back.
1103    /// mode: 0b00=nearest, 0b01=floor(-Inf), 0b10=ceil(+Inf), 0b11=trunc(zero)
1104    /// Strategy: VCVT.S32.F32 Sd, Sm (toward zero), then VCVT.F32.S32 Sd, Sd
1105    /// For ceil/floor/nearest, we use VCVTR (round toward mode) + convert back.
1106    /// Simplified: convert to int (toward zero for trunc) then back to float.
1107    /// Encode F32 rounding as ARM32.
1108    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
1109    ///
1110    /// For trunc (mode=0b11): uses VCVTR.S32.F32 (always rounds toward zero).
1111    /// For ceil/floor/nearest: sets FPSCR rounding mode, uses VCVT.S32.F32 (non-R variant
1112    /// which honours FPSCR rmode), then restores FPSCR.
1113    fn encode_arm_f32_rounding(&self, sd: &VfpReg, sm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
1114        let mut bytes = Vec::new();
1115        let sm_num = vfp_sreg_to_num(sm)?;
1116        let sd_num = vfp_sreg_to_num(sd)?;
1117        let (vd_s, d_s) = encode_sreg(sd_num);
1118        let (vm_s, m_s) = encode_sreg(sm_num);
1119
1120        if mode == 0b11 {
1121            // Trunc (toward zero): VCVTR.S32.F32 — the "R" variant always truncates.
1122            // 0xEEBD0AC0: bit[7]=1 => round toward zero regardless of FPSCR
1123            let vcvt_to_int = 0xEEBD0AC0 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
1124            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1125        } else {
1126            // ceil/floor/nearest: manipulate FPSCR rounding mode
1127            let rt: u32 = 12; // R12/IP as temp
1128
1129            // VMRS R12, FPSCR
1130            let vmrs = 0xEEF10A10 | (rt << 12);
1131            bytes.extend_from_slice(&vmrs.to_le_bytes());
1132
1133            // BIC R12, R12, #(3 << 22) — clear RMode bits [23:22]
1134            // 3<<22 = 0x00C00000. ARM rotated imm: 0x03 ror 10 (rotation=5, imm8=0x03)
1135            let bic = 0xE3CC0000 | (rt << 12) | (0x05 << 8) | 0x03;
1136            bytes.extend_from_slice(&bic.to_le_bytes());
1137
1138            // ORR R12, R12, #(mode << 22) — set desired rounding mode
1139            if mode != 0 {
1140                // mode<<22: rotation=5, imm8=mode
1141                let orr = 0xE38C0000 | (rt << 12) | (0x05 << 8) | (mode as u32);
1142                bytes.extend_from_slice(&orr.to_le_bytes());
1143            }
1144
1145            // VMSR FPSCR, R12
1146            let vmsr = 0xEEE10A10 | (rt << 12);
1147            bytes.extend_from_slice(&vmsr.to_le_bytes());
1148
1149            // VCVT.S32.F32 Sd, Sm — non-R variant (bit[7]=0), uses FPSCR rounding mode
1150            let vcvt_to_int = 0xEEBD0A40 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
1151            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1152
1153            // Restore FPSCR: clear rmode bits back to nearest (default)
1154            bytes.extend_from_slice(&vmrs.to_le_bytes());
1155            bytes.extend_from_slice(&bic.to_le_bytes());
1156            bytes.extend_from_slice(&vmsr.to_le_bytes());
1157        }
1158
1159        // VCVT.F32.S32 Sd, Sd (convert integer result back to float)
1160        let (vd2, d2) = encode_sreg(sd_num);
1161        let vcvt_to_float = 0xEEB80A40 | (d2 << 22) | (vd2 << 12) | (d_s << 5) | vd_s;
1162        bytes.extend_from_slice(&vcvt_to_float.to_le_bytes());
1163
1164        Ok(bytes)
1165    }
1166
1167    /// Encode F32 min/max as ARM32: VCMP + VMRS + conditional VMOV
1168    fn encode_arm_f32_minmax(
1169        &self,
1170        sd: &VfpReg,
1171        sn: &VfpReg,
1172        sm: &VfpReg,
1173        is_min: bool,
1174    ) -> Result<Vec<u8>> {
1175        let mut bytes = Vec::new();
1176        let sn_num = vfp_sreg_to_num(sn)?;
1177        let sm_num = vfp_sreg_to_num(sm)?;
1178        let sd_num = vfp_sreg_to_num(sd)?;
1179
1180        // VMOV Sd, Sn (start with first operand)
1181        let (vd, d) = encode_sreg(sd_num);
1182        let (vn, n) = encode_sreg(sn_num);
1183        let vmov_sn = 0xEEB00A40 | (d << 22) | (vd << 12) | (n << 5) | vn;
1184        bytes.extend_from_slice(&vmov_sn.to_le_bytes());
1185
1186        // VCMP.F32 Sn, Sm
1187        let (vm, m) = encode_sreg(sm_num);
1188        let vcmp = 0xEEB40A40 | (n << 22) | (vn << 12) | (m << 5) | vm;
1189        bytes.extend_from_slice(&vcmp.to_le_bytes());
1190
1191        // VMRS APSR_nzcv, FPSCR
1192        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1193
1194        // For min: if Sn > Sm (GT), use Sm. Condition = GT (0xC)
1195        // For max: if Sn < Sm (MI/LT), use Sm. Condition = MI (0x4)
1196        let cond = if is_min { 0xCu32 } else { 0x4u32 };
1197
1198        // VMOV{cond} Sd, Sm — conditional VMOV
1199        let vmov_cond = (cond << 28) | 0x0EB00A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1200        bytes.extend_from_slice(&vmov_cond.to_le_bytes());
1201
1202        Ok(bytes)
1203    }
1204
1205    /// Encode F32 copysign as ARM32: extract sign from Sm, magnitude from Sn
1206    fn encode_arm_f32_copysign(&self, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
1207        let mut bytes = Vec::new();
1208
1209        // VMOV R12, Sm (get sign source bits)
1210        let vmov_sm = encode_vmov_core_sreg(false, sm, &Reg::R12)?;
1211        bytes.extend_from_slice(&vmov_sm.to_le_bytes());
1212
1213        // VMOV R0, Sn (get magnitude source bits) — use R0 as temp
1214        let vmov_sn = encode_vmov_core_sreg(false, sn, &Reg::R0)?;
1215        bytes.extend_from_slice(&vmov_sn.to_le_bytes());
1216
1217        // AND R12, R12, #0x80000000 (keep only sign bit)
1218        // Thumb-2 constant 0x80000000 needs special encoding; in ARM32 use rotated imm
1219        // 0x80000000 = 0x02 rotated right by 2 (rotation=1, imm8=0x02)
1220        let and_sign = 0xE2000000u32 | (12 << 16) | (12 << 12) | (1 << 8) | 0x02;
1221        bytes.extend_from_slice(&and_sign.to_le_bytes());
1222
1223        // BIC R0, R0, #0x80000000 (clear sign bit from magnitude)
1224        // R0 = register 0, so Rn and Rd fields are 0
1225        let bic_sign = 0xE3C00000u32 | (1 << 8) | 0x02;
1226        bytes.extend_from_slice(&bic_sign.to_le_bytes());
1227
1228        // ORR R0, R0, R12 (combine sign + magnitude)
1229        // R0 = register 0, so Rn and Rd fields are 0
1230        let orr = 0xE1800000u32 | 12;
1231        bytes.extend_from_slice(&orr.to_le_bytes());
1232
1233        // VMOV Sd, R0
1234        let vmov_result = encode_vmov_core_sreg(true, sd, &Reg::R0)?;
1235        bytes.extend_from_slice(&vmov_result.to_le_bytes());
1236
1237        Ok(bytes)
1238    }
1239
1240    /// Encode F64 comparison as ARM32: VCMP.F64 + VMRS + MOV rd,#0 + MOVcond rd,#1
1241    fn encode_arm_f64_compare(
1242        &self,
1243        rd: &Reg,
1244        dn: &VfpReg,
1245        dm: &VfpReg,
1246        cond_code: u32,
1247    ) -> Result<Vec<u8>> {
1248        let mut bytes = Vec::new();
1249
1250        // VCMP.F64 Dn, Dm: 0xEEB40B40 with Dn in Vd position, Dm in Vm position
1251        let dn_num = vfp_dreg_to_num(dn)?;
1252        let dm_num = vfp_dreg_to_num(dm)?;
1253        let (vd, d) = encode_dreg(dn_num);
1254        let (vm, m) = encode_dreg(dm_num);
1255        let vcmp = 0xEEB40B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1256        bytes.extend_from_slice(&vcmp.to_le_bytes());
1257
1258        // VMRS APSR_nzcv, FPSCR
1259        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1260
1261        // MOV rd, #0
1262        let rd_bits = reg_to_bits(rd);
1263        let mov_zero = 0xE3A00000 | (rd_bits << 12);
1264        bytes.extend_from_slice(&mov_zero.to_le_bytes());
1265
1266        // MOVcond rd, #1
1267        let mov_one = (cond_code << 28) | 0x03A00001 | (rd_bits << 12);
1268        bytes.extend_from_slice(&mov_one.to_le_bytes());
1269
1270        Ok(bytes)
1271    }
1272
1273    /// Encode F64 constant load as ARM32: MOVW + MOVT + MOVW + MOVT + VMOV
1274    fn encode_arm_f64_const(&self, dd: &VfpReg, value: f64) -> Result<Vec<u8>> {
1275        let mut bytes = Vec::new();
1276        let bits = value.to_bits();
1277        let lo32 = bits as u32;
1278        let hi32 = (bits >> 32) as u32;
1279
1280        // Load low 32 bits into R0 (Rd field = 0 for R0)
1281        let lo16 = lo32 & 0xFFFF;
1282        let movw_r0 = 0xE3000000 | ((lo16 >> 12) << 16) | (lo16 & 0xFFF);
1283        bytes.extend_from_slice(&movw_r0.to_le_bytes());
1284        let hi16 = (lo32 >> 16) & 0xFFFF;
1285        let movt_r0 = 0xE3400000 | ((hi16 >> 12) << 16) | (hi16 & 0xFFF);
1286        bytes.extend_from_slice(&movt_r0.to_le_bytes());
1287
1288        // Load high 32 bits into R12
1289        let lo16 = hi32 & 0xFFFF;
1290        let movw_r12 = 0xE3000000 | ((lo16 >> 12) << 16) | (12 << 12) | (lo16 & 0xFFF);
1291        bytes.extend_from_slice(&movw_r12.to_le_bytes());
1292        let hi16 = (hi32 >> 16) & 0xFFFF;
1293        let movt_r12 = 0xE3400000 | ((hi16 >> 12) << 16) | (12 << 12) | (hi16 & 0xFFF);
1294        bytes.extend_from_slice(&movt_r12.to_le_bytes());
1295
1296        // VMOV Dd, R0, R12
1297        let vmov = encode_vmov_core_dreg(true, dd, &Reg::R0, &Reg::R12)?;
1298        bytes.extend_from_slice(&vmov.to_le_bytes());
1299
1300        Ok(bytes)
1301    }
1302
1303    /// Encode VMOV Sd, Rm + VCVT.F64.S32/U32 Dd, Sd as ARM32
1304    fn encode_arm_f64_convert_i32(&self, dd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
1305        let mut bytes = Vec::new();
1306
1307        // Use S0 as intermediate: VMOV S0, Rm
1308        let vmov = encode_vmov_core_sreg(true, &VfpReg::S0, rm)?;
1309        bytes.extend_from_slice(&vmov.to_le_bytes());
1310
1311        // VCVT.F64.S32 Dd, S0 (signed) or VCVT.F64.U32 Dd, S0 (unsigned)
1312        // Base: 0xEEB80B40 (signed) or 0xEEB80BC0 (unsigned)
1313        let dd_num = vfp_dreg_to_num(dd)?;
1314        let (vd, d) = encode_dreg(dd_num);
1315        let base = if signed { 0xEEB80B40 } else { 0xEEB80BC0 };
1316        // S0 is register 0: Vm=0, M=0
1317        let vcvt = base | (d << 22) | (vd << 12);
1318        bytes.extend_from_slice(&vcvt.to_le_bytes());
1319
1320        Ok(bytes)
1321    }
1322
1323    /// Encode VCVT.F64.F32 Dd, Sm as ARM32 (f32 to f64 promotion)
1324    fn encode_arm_f64_promote_f32(&self, dd: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
1325        let dd_num = vfp_dreg_to_num(dd)?;
1326        let sm_num = vfp_sreg_to_num(sm)?;
1327        let (vd, d) = encode_dreg(dd_num);
1328        let (vm, m) = encode_sreg(sm_num);
1329
1330        // VCVT.F64.F32 Dd, Sm: 0xEEB70AC0
1331        let vcvt = 0xEEB70AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
1332        Ok(vcvt.to_le_bytes().to_vec())
1333    }
1334
1335    /// Encode VCVT.S32/U32.F64 Sd, Dm + VMOV Rd, Sd as ARM32
1336    fn encode_arm_i32_trunc_f64(&self, rd: &Reg, dm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
1337        let mut bytes = Vec::new();
1338        let dm_num = vfp_dreg_to_num(dm)?;
1339        let (vm, m) = encode_dreg(dm_num);
1340
1341        // VCVT.S32.F64 S0, Dm (toward zero) or VCVT.U32.F64 S0, Dm
1342        // S0: Vd=0, D=0
1343        let base = if signed { 0xEEBD0BC0 } else { 0xEEBC0BC0 };
1344        let vcvt = base | (m << 5) | vm;
1345        bytes.extend_from_slice(&vcvt.to_le_bytes());
1346
1347        // VMOV Rd, S0
1348        let vmov = encode_vmov_core_sreg(false, &VfpReg::S0, rd)?;
1349        bytes.extend_from_slice(&vmov.to_le_bytes());
1350
1351        Ok(bytes)
1352    }
1353
1354    /// Encode F64 rounding pseudo-op as ARM32 via VCVT to integer and back.
1355    /// Encode F64 rounding as ARM32.
1356    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
1357    ///
1358    /// For trunc: uses VCVTR.S32.F64 (always truncates).
1359    /// For ceil/floor/nearest: sets FPSCR rounding mode, uses VCVT.S32.F64 (non-R variant),
1360    /// then restores FPSCR.
1361    fn encode_arm_f64_rounding(&self, dd: &VfpReg, dm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
1362        let mut bytes = Vec::new();
1363        let dm_num = vfp_dreg_to_num(dm)?;
1364        let dd_num = vfp_dreg_to_num(dd)?;
1365        let (vm, m) = encode_dreg(dm_num);
1366        let (vd, d) = encode_dreg(dd_num);
1367
1368        if mode == 0b11 {
1369            // Trunc (toward zero): VCVTR.S32.F64 — bit[7]=1, always truncates
1370            let vcvt_to_int = 0xEEBD0BC0 | (m << 5) | vm;
1371            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1372        } else {
1373            // ceil/floor/nearest: manipulate FPSCR rounding mode
1374            let rt: u32 = 12;
1375
1376            // VMRS R12, FPSCR
1377            let vmrs = 0xEEF10A10 | (rt << 12);
1378            bytes.extend_from_slice(&vmrs.to_le_bytes());
1379
1380            // BIC R12, R12, #(3 << 22)
1381            let bic = 0xE3CC0000 | (rt << 12) | (0x05 << 8) | 0x03;
1382            bytes.extend_from_slice(&bic.to_le_bytes());
1383
1384            // ORR R12, R12, #(mode << 22)
1385            if mode != 0 {
1386                let orr = 0xE38C0000 | (rt << 12) | (0x05 << 8) | (mode as u32);
1387                bytes.extend_from_slice(&orr.to_le_bytes());
1388            }
1389
1390            // VMSR FPSCR, R12
1391            let vmsr = 0xEEE10A10 | (rt << 12);
1392            bytes.extend_from_slice(&vmsr.to_le_bytes());
1393
1394            // VCVT.S32.F64 S0, Dm — non-R variant (bit[7]=0), uses FPSCR rmode
1395            let vcvt_to_int = 0xEEBD0B40 | (m << 5) | vm;
1396            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1397
1398            // Restore FPSCR
1399            bytes.extend_from_slice(&vmrs.to_le_bytes());
1400            bytes.extend_from_slice(&bic.to_le_bytes());
1401            bytes.extend_from_slice(&vmsr.to_le_bytes());
1402        }
1403
1404        // VCVT.F64.S32 Dd, S0 (convert back to double)
1405        let vcvt_to_float = 0xEEB80B40 | (d << 22) | (vd << 12);
1406        bytes.extend_from_slice(&vcvt_to_float.to_le_bytes());
1407
1408        Ok(bytes)
1409    }
1410
1411    /// Encode F64 min/max as ARM32: VMOV + VCMP + VMRS + conditional VMOV
1412    fn encode_arm_f64_minmax(
1413        &self,
1414        dd: &VfpReg,
1415        dn: &VfpReg,
1416        dm: &VfpReg,
1417        is_min: bool,
1418    ) -> Result<Vec<u8>> {
1419        let mut bytes = Vec::new();
1420        let dn_num = vfp_dreg_to_num(dn)?;
1421        let dm_num = vfp_dreg_to_num(dm)?;
1422        let dd_num = vfp_dreg_to_num(dd)?;
1423
1424        // VMOV.F64 Dd, Dn (start with first operand)
1425        let (vd, d) = encode_dreg(dd_num);
1426        let (vn, n) = encode_dreg(dn_num);
1427        let vmov_dn = 0xEEB00B40 | (d << 22) | (vd << 12) | (n << 5) | vn;
1428        bytes.extend_from_slice(&vmov_dn.to_le_bytes());
1429
1430        // VCMP.F64 Dn, Dm
1431        let (vm, m) = encode_dreg(dm_num);
1432        let vcmp = 0xEEB40B40 | (n << 22) | (vn << 12) | (m << 5) | vm;
1433        bytes.extend_from_slice(&vcmp.to_le_bytes());
1434
1435        // VMRS APSR_nzcv, FPSCR
1436        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1437
1438        let cond = if is_min { 0xCu32 } else { 0x4u32 };
1439        let vmov_cond = (cond << 28) | 0x0EB00B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1440        bytes.extend_from_slice(&vmov_cond.to_le_bytes());
1441
1442        Ok(bytes)
1443    }
1444
1445    /// Encode F64 copysign as ARM32
1446    fn encode_arm_f64_copysign(&self, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<Vec<u8>> {
1447        let mut bytes = Vec::new();
1448
1449        // VMOV R0, R12, Dm (get sign source bits)
1450        let vmov_dm = encode_vmov_core_dreg(false, dm, &Reg::R0, &Reg::R12)?;
1451        bytes.extend_from_slice(&vmov_dm.to_le_bytes());
1452
1453        // VMOV R1, R2, Dn (get magnitude source bits)
1454        // We use R1 (lo) and R2 (hi) for the magnitude
1455        let vmov_dn = encode_vmov_core_dreg(false, dn, &Reg::R1, &Reg::R2)?;
1456        bytes.extend_from_slice(&vmov_dn.to_le_bytes());
1457
1458        // AND R12, R12, #0x80000000 (keep only sign bit from hi word)
1459        let and_sign = 0xE2000000u32 | (12 << 16) | (12 << 12) | (1 << 8) | 0x02;
1460        bytes.extend_from_slice(&and_sign.to_le_bytes());
1461
1462        // BIC R2, R2, #0x80000000 (clear sign bit from magnitude hi word)
1463        let bic_sign = 0xE3C00000u32 | (2 << 16) | (2 << 12) | (1 << 8) | 0x02;
1464        bytes.extend_from_slice(&bic_sign.to_le_bytes());
1465
1466        // ORR R2, R2, R12 (combine sign + magnitude)
1467        let orr = 0xE1800000u32 | (2 << 16) | (2 << 12) | 12;
1468        bytes.extend_from_slice(&orr.to_le_bytes());
1469
1470        // VMOV Dd, R1, R2
1471        let vmov_result = encode_vmov_core_dreg(true, dd, &Reg::R1, &Reg::R2)?;
1472        bytes.extend_from_slice(&vmov_result.to_le_bytes());
1473
1474        Ok(bytes)
1475    }
1476
1477    /// Encode VCVT.S32/U32.F32 + VMOV as ARM32
1478    fn encode_arm_i32_trunc_f32(&self, rd: &Reg, sm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
1479        let mut bytes = Vec::new();
1480
1481        // VCVT.S32.F32 Sd, Sm (toward zero) or VCVT.U32.F32 Sd, Sm
1482        // We use Sm as both source and destination for the intermediate result
1483        let sm_num = vfp_sreg_to_num(sm)?;
1484        let (vd, d) = encode_sreg(sm_num);
1485        let (vm, m) = encode_sreg(sm_num);
1486        let base = if signed { 0xEEBD0AC0 } else { 0xEEBC0AC0 };
1487        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
1488        bytes.extend_from_slice(&vcvt.to_le_bytes());
1489
1490        // VMOV Rd, Sm — move result back to core register
1491        let vmov = encode_vmov_core_sreg(false, sm, rd)?;
1492        bytes.extend_from_slice(&vmov.to_le_bytes());
1493
1494        Ok(bytes)
1495    }
1496
1497    /// Encode an ARM instruction in Thumb-2 mode (16-bit or 32-bit instructions)
1498    fn encode_thumb(&self, op: &ArmOp) -> Result<Vec<u8>> {
1499        // Thumb-2 supports both 16-bit and 32-bit instructions
1500        // 32-bit instructions are encoded as two 16-bit halfwords (big-endian order)
1501        match op {
1502            // === 16-bit Thumb encodings ===
1503            ArmOp::Add { rd, rn, op2 } => {
1504                let rd_bits = reg_to_bits(rd) as u16;
1505                let rn_bits = reg_to_bits(rn) as u16;
1506
1507                if let Operand2::Reg(rm) = op2 {
1508                    let rm_bits = reg_to_bits(rm) as u16;
1509                    // 16-bit ADDS only has 3-bit register fields (R0-R7). For
1510                    // high registers (e.g. R12, the MemLoad/MemStore base
1511                    // scratch) the bits overflow into adjacent fields, silently
1512                    // corrupting the operands — issue #178/#180: `add ip,ip,r0`
1513                    // was emitted as `adds r4,r5,r1`. Guard on all three regs
1514                    // being low and fall back to 32-bit ADD.W otherwise, exactly
1515                    // as the Sub handler below does.
1516                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1517                        // ADDS Rd, Rn, Rm (16-bit): 0001 100 Rm Rn Rd
1518                        let instr: u16 = 0x1800 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1519                        Ok(instr.to_le_bytes().to_vec())
1520                    } else {
1521                        // ADD.W Rd, Rn, Rm (32-bit) for high registers
1522                        self.encode_thumb32_add_reg_raw(
1523                            rd_bits as u32,
1524                            rn_bits as u32,
1525                            rm_bits as u32,
1526                        )
1527                    }
1528                } else if let Operand2::Imm(imm) = op2 {
1529                    if *imm <= 7 && rd_bits < 8 && rn_bits < 8 {
1530                        // ADDS Rd, Rn, #imm3 (16-bit): 0001 110 imm3 Rn Rd
1531                        let instr: u16 = 0x1C00 | ((*imm as u16) << 6) | (rn_bits << 3) | rd_bits;
1532                        Ok(instr.to_le_bytes().to_vec())
1533                    } else {
1534                        // Use 32-bit ADD for larger immediates
1535                        self.encode_thumb32_add(rd, rn, *imm as u32)
1536                    }
1537                } else {
1538                    // Fallback to 32-bit encoding
1539                    self.encode_thumb32_add(rd, rn, 0)
1540                }
1541            }
1542
1543            ArmOp::Sub { rd, rn, op2 } => {
1544                let rd_bits = reg_to_bits(rd) as u16;
1545                let rn_bits = reg_to_bits(rn) as u16;
1546
1547                if let Operand2::Reg(rm) = op2 {
1548                    let rm_bits = reg_to_bits(rm) as u16;
1549                    // 16-bit SUBS can only use low registers (R0-R7)
1550                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1551                        // SUBS Rd, Rn, Rm (16-bit): 0001 101 Rm Rn Rd
1552                        let instr: u16 = 0x1A00 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1553                        Ok(instr.to_le_bytes().to_vec())
1554                    } else {
1555                        // Use 32-bit SUB.W for high registers
1556                        self.encode_thumb32_sub_reg_raw(
1557                            rd_bits as u32,
1558                            rn_bits as u32,
1559                            rm_bits as u32,
1560                        )
1561                    }
1562                } else if let Operand2::Imm(imm) = op2 {
1563                    if *imm <= 7 && rd_bits < 8 && rn_bits < 8 {
1564                        // SUBS Rd, Rn, #imm3 (16-bit): 0001 111 imm3 Rn Rd
1565                        let instr: u16 = 0x1E00 | ((*imm as u16) << 6) | (rn_bits << 3) | rd_bits;
1566                        Ok(instr.to_le_bytes().to_vec())
1567                    } else {
1568                        self.encode_thumb32_sub(rd, rn, *imm as u32)
1569                    }
1570                } else {
1571                    self.encode_thumb32_sub(rd, rn, 0)
1572                }
1573            }
1574
1575            ArmOp::Mov { rd, op2 } => {
1576                let rd_bits = reg_to_bits(rd) as u16;
1577
1578                if let Operand2::Imm(imm) = op2 {
1579                    if *imm <= 255 && rd_bits < 8 {
1580                        // MOVS Rd, #imm8 (16-bit): 0010 0 Rd imm8
1581                        let imm_bits = (*imm as u16) & 0xFF;
1582                        let instr: u16 = 0x2000 | (rd_bits << 8) | imm_bits;
1583                        Ok(instr.to_le_bytes().to_vec())
1584                    } else {
1585                        // Use 32-bit MOVW for larger immediates
1586                        self.encode_thumb32_movw(rd, *imm as u32)
1587                    }
1588                } else if let Operand2::Reg(rm) = op2 {
1589                    let rm_bits = reg_to_bits(rm) as u16;
1590                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
1591                    // D = Rd[3], Rd[2:0] in lower bits
1592                    let d_bit = (rd_bits >> 3) & 1;
1593                    let instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
1594                    Ok(instr.to_le_bytes().to_vec())
1595                } else {
1596                    let instr: u16 = 0xBF00; // NOP fallback
1597                    Ok(instr.to_le_bytes().to_vec())
1598                }
1599            }
1600
1601            ArmOp::Push { regs } => {
1602                // Thumb-2 PUSH encoding:
1603                // If all regs in R0-R7 + LR, use 16-bit: 1011 010 M rrrrrrrr
1604                // Otherwise use 32-bit: STMDB SP!, {regs} = 1110 1001 0010 1101 | 0M0 reglist(13)
1605                let mut reg_list: u16 = 0;
1606                let mut need_32bit = false;
1607                for r in regs {
1608                    let bit = reg_to_bits(r);
1609                    if bit >= 8 && *r != Reg::LR {
1610                        need_32bit = true;
1611                    }
1612                    reg_list |= 1 << bit;
1613                }
1614                if !need_32bit {
1615                    // 16-bit PUSH: 1011 010 M rrrrrrrr
1616                    let m_bit = if reg_list & (1 << 14) != 0 {
1617                        1u16
1618                    } else {
1619                        0u16
1620                    };
1621                    let low_regs = reg_list & 0xFF;
1622                    let instr: u16 = 0xB400 | (m_bit << 8) | low_regs;
1623                    Ok(instr.to_le_bytes().to_vec())
1624                } else {
1625                    // 32-bit STMDB SP!, {regs}: E92D | reglist(16)
1626                    let hw1: u16 = 0xE92D;
1627                    let hw2: u16 = reg_list;
1628                    let mut bytes = hw1.to_le_bytes().to_vec();
1629                    bytes.extend_from_slice(&hw2.to_le_bytes());
1630                    Ok(bytes)
1631                }
1632            }
1633
1634            ArmOp::Pop { regs } => {
1635                // Thumb-2 POP encoding:
1636                // If all regs in R0-R7 + PC, use 16-bit: 1011 110 P rrrrrrrr
1637                // Otherwise use 32-bit: LDMIA SP!, {regs} = 1110 1000 1011 1101 | PM0 reglist(13)
1638                let mut reg_list: u16 = 0;
1639                let mut need_32bit = false;
1640                for r in regs {
1641                    let bit = reg_to_bits(r);
1642                    if bit >= 8 && *r != Reg::PC {
1643                        need_32bit = true;
1644                    }
1645                    reg_list |= 1 << bit;
1646                }
1647                if !need_32bit {
1648                    // 16-bit POP: 1011 110 P rrrrrrrr
1649                    let p_bit = if reg_list & (1 << 15) != 0 {
1650                        1u16
1651                    } else {
1652                        0u16
1653                    };
1654                    let low_regs = reg_list & 0xFF;
1655                    let instr: u16 = 0xBC00 | (p_bit << 8) | low_regs;
1656                    Ok(instr.to_le_bytes().to_vec())
1657                } else {
1658                    // 32-bit LDMIA SP!, {regs}: E8BD | reglist(16)
1659                    let hw1: u16 = 0xE8BD;
1660                    let hw2: u16 = reg_list;
1661                    let mut bytes = hw1.to_le_bytes().to_vec();
1662                    bytes.extend_from_slice(&hw2.to_le_bytes());
1663                    Ok(bytes)
1664                }
1665            }
1666
1667            ArmOp::Nop => {
1668                let instr: u16 = 0xBF00; // NOP in Thumb-2
1669                Ok(instr.to_le_bytes().to_vec())
1670            }
1671
1672            ArmOp::Udf { imm } => {
1673                // UDF (Undefined) in Thumb-2: 16-bit encoding is 0xDE00 | imm8
1674                // This triggers UsageFault/HardFault, used for WASM traps
1675                let instr: u16 = 0xDE00 | (*imm as u16);
1676                let bytes = instr.to_le_bytes().to_vec();
1677                encoding_contracts::verify_thumb16(&bytes);
1678                Ok(bytes)
1679            }
1680
1681            // i64 support: ADDS, ADC, SUBS, SBC for register pair arithmetic
1682            // ADDS sets flags (carry), ADC uses carry from previous ADDS
1683            ArmOp::Adds { rd, rn, op2 } => {
1684                let rd_bits = reg_to_bits(rd) as u16;
1685                let rn_bits = reg_to_bits(rn) as u16;
1686
1687                if let Operand2::Reg(rm) = op2 {
1688                    let rm_bits = reg_to_bits(rm) as u16;
1689                    // 16-bit ADDS is R0-R7 only; i64 pair allocation can place
1690                    // operands in R8-R11, which would overflow the 3-bit fields
1691                    // and corrupt the operands (#178/#180 class). Guard and fall
1692                    // back to 32-bit ADDS.W for high registers.
1693                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1694                        // ADDS Rd, Rn, Rm (16-bit): 0001 100 Rm Rn Rd
1695                        let instr: u16 = 0x1800 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1696                        Ok(instr.to_le_bytes().to_vec())
1697                    } else {
1698                        self.encode_thumb32_adds_reg_raw(
1699                            rd_bits as u32,
1700                            rn_bits as u32,
1701                            rm_bits as u32,
1702                        )
1703                    }
1704                } else {
1705                    // 32-bit Thumb-2 ADDS with immediate
1706                    self.encode_thumb32_adds(rd, rn, 0)
1707                }
1708            }
1709
1710            // ADC: Add with Carry (Thumb-2 32-bit)
1711            // ADC.W Rd, Rn, Rm: EB40 Rn | 00 Rd 00 Rm
1712            ArmOp::Adc { rd, rn, op2 } => {
1713                let rd_bits = reg_to_bits(rd);
1714                let rn_bits = reg_to_bits(rn);
1715
1716                if let Operand2::Reg(rm) = op2 {
1717                    let rm_bits = reg_to_bits(rm);
1718                    // ADC.W Rd, Rn, Rm (T2): 1110 1011 0100 Rn | 0 000 Rd 00 00 Rm
1719                    let hw1: u16 = (0xEB40 | rn_bits) as u16;
1720                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1721
1722                    let mut bytes = hw1.to_le_bytes().to_vec();
1723                    bytes.extend_from_slice(&hw2.to_le_bytes());
1724                    Ok(bytes)
1725                } else {
1726                    // ADC with immediate - use 32-bit encoding
1727                    let hw1: u16 = (0xF140 | rn_bits) as u16;
1728                    let hw2: u16 = (rd_bits << 8) as u16;
1729                    let mut bytes = hw1.to_le_bytes().to_vec();
1730                    bytes.extend_from_slice(&hw2.to_le_bytes());
1731                    Ok(bytes)
1732                }
1733            }
1734
1735            // SUBS sets flags (borrow), SBC uses borrow from previous SUBS
1736            ArmOp::Subs { rd, rn, op2 } => {
1737                let rd_bits = reg_to_bits(rd) as u16;
1738                let rn_bits = reg_to_bits(rn) as u16;
1739
1740                if let Operand2::Reg(rm) = op2 {
1741                    let rm_bits = reg_to_bits(rm) as u16;
1742                    // 16-bit SUBS is R0-R7 only; high-register i64 pair operands
1743                    // would overflow the 3-bit fields (#178/#180 class). Guard
1744                    // and fall back to 32-bit SUBS.W for high registers.
1745                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1746                        // SUBS Rd, Rn, Rm (16-bit): 0001 101 Rm Rn Rd
1747                        let instr: u16 = 0x1A00 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1748                        Ok(instr.to_le_bytes().to_vec())
1749                    } else {
1750                        self.encode_thumb32_subs_reg_raw(
1751                            rd_bits as u32,
1752                            rn_bits as u32,
1753                            rm_bits as u32,
1754                        )
1755                    }
1756                } else {
1757                    // 32-bit Thumb-2 SUBS with immediate
1758                    self.encode_thumb32_subs(rd, rn, 0)
1759                }
1760            }
1761
1762            // SBC: Subtract with Carry (Thumb-2 32-bit)
1763            // SBC.W Rd, Rn, Rm: EB60 Rn | 00 Rd 00 Rm
1764            ArmOp::Sbc { rd, rn, op2 } => {
1765                let rd_bits = reg_to_bits(rd);
1766                let rn_bits = reg_to_bits(rn);
1767
1768                if let Operand2::Reg(rm) = op2 {
1769                    let rm_bits = reg_to_bits(rm);
1770                    // SBC.W Rd, Rn, Rm (T2): 1110 1011 0110 Rn | 0 000 Rd 00 00 Rm
1771                    let hw1: u16 = (0xEB60 | rn_bits) as u16;
1772                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1773
1774                    let mut bytes = hw1.to_le_bytes().to_vec();
1775                    bytes.extend_from_slice(&hw2.to_le_bytes());
1776                    Ok(bytes)
1777                } else {
1778                    // SBC with immediate - use 32-bit encoding
1779                    let hw1: u16 = (0xF160 | rn_bits) as u16;
1780                    let hw2: u16 = (rd_bits << 8) as u16;
1781                    let mut bytes = hw1.to_le_bytes().to_vec();
1782                    bytes.extend_from_slice(&hw2.to_le_bytes());
1783                    Ok(bytes)
1784                }
1785            }
1786
1787            // === 32-bit Thumb-2 encodings ===
1788
1789            // SDIV: 11111011 1001 Rn 1111 Rd 1111 Rm
1790            ArmOp::Sdiv { rd, rn, rm } => {
1791                let rd_bits = reg_to_bits(rd);
1792                let rn_bits = reg_to_bits(rn);
1793                let rm_bits = reg_to_bits(rm);
1794                reg_bits_checked(rd_bits)?;
1795                reg_bits_checked(rn_bits)?;
1796                reg_bits_checked(rm_bits)?;
1797
1798                // Thumb-2 SDIV: FB90 F0F0 | Rn<<16 | Rd<<8 | Rm
1799                // First halfword: 1111 1011 1001 Rn = 0xFB90 | Rn
1800                // Second halfword: 1111 Rd 1111 Rm = 0xF0F0 | Rd<<8 | Rm
1801                let hw1: u16 = (0xFB90 | rn_bits) as u16;
1802                let hw2: u16 = (0xF0F0 | (rd_bits << 8) | rm_bits) as u16;
1803
1804                // Thumb-2 32-bit instructions: first halfword, then second halfword (little-endian each)
1805                let mut bytes = hw1.to_le_bytes().to_vec();
1806                bytes.extend_from_slice(&hw2.to_le_bytes());
1807                encoding_contracts::verify_thumb32(&bytes);
1808                Ok(bytes)
1809            }
1810
1811            // UDIV: 11111011 1011 Rn 1111 Rd 1111 Rm
1812            ArmOp::Udiv { rd, rn, rm } => {
1813                let rd_bits = reg_to_bits(rd);
1814                let rn_bits = reg_to_bits(rn);
1815                let rm_bits = reg_to_bits(rm);
1816                reg_bits_checked(rd_bits)?;
1817                reg_bits_checked(rn_bits)?;
1818                reg_bits_checked(rm_bits)?;
1819
1820                // Thumb-2 UDIV: FBB0 F0F0 | Rn<<16 | Rd<<8 | Rm
1821                let hw1: u16 = (0xFBB0 | rn_bits) as u16;
1822                let hw2: u16 = (0xF0F0 | (rd_bits << 8) | rm_bits) as u16;
1823
1824                let mut bytes = hw1.to_le_bytes().to_vec();
1825                bytes.extend_from_slice(&hw2.to_le_bytes());
1826                encoding_contracts::verify_thumb32(&bytes);
1827                Ok(bytes)
1828            }
1829
1830            ArmOp::Umull { rdlo, rdhi, rn, rm } => {
1831                let rdlo_bits = reg_to_bits(rdlo);
1832                let rdhi_bits = reg_to_bits(rdhi);
1833                let rn_bits = reg_to_bits(rn);
1834                let rm_bits = reg_to_bits(rm);
1835                reg_bits_checked(rdlo_bits)?;
1836                reg_bits_checked(rdhi_bits)?;
1837                reg_bits_checked(rn_bits)?;
1838                reg_bits_checked(rm_bits)?;
1839
1840                // Thumb-2 UMULL: 1111 1011 1010 Rn | RdLo RdHi 0000 Rm
1841                let hw1: u16 = (0xFBA0 | rn_bits) as u16;
1842                let hw2: u16 = ((rdlo_bits << 12) | (rdhi_bits << 8) | rm_bits) as u16;
1843
1844                let mut bytes = hw1.to_le_bytes().to_vec();
1845                bytes.extend_from_slice(&hw2.to_le_bytes());
1846                encoding_contracts::verify_thumb32(&bytes);
1847                Ok(bytes)
1848            }
1849
1850            // MUL (Thumb-2 32-bit): MUL Rd, Rn, Rm
1851            ArmOp::Mul { rd, rn, rm } => {
1852                let rd_bits = reg_to_bits(rd);
1853                let rn_bits = reg_to_bits(rn);
1854                let rm_bits = reg_to_bits(rm);
1855
1856                // Thumb-2 MUL: FB00 F000 | Rn | Rd<<8 | Rm
1857                // 11111011 0000 Rn | 1111 Rd 0000 Rm
1858                let hw1: u16 = (0xFB00 | rn_bits) as u16;
1859                let hw2: u16 = (0xF000 | (rd_bits << 8) | rm_bits) as u16;
1860
1861                let mut bytes = hw1.to_le_bytes().to_vec();
1862                bytes.extend_from_slice(&hw2.to_le_bytes());
1863                Ok(bytes)
1864            }
1865
1866            // MLS: Rd = Ra - Rn * Rm
1867            ArmOp::Mls { rd, rn, rm, ra } => {
1868                let rd_bits = reg_to_bits(rd);
1869                let rn_bits = reg_to_bits(rn);
1870                let rm_bits = reg_to_bits(rm);
1871                let ra_bits = reg_to_bits(ra);
1872
1873                // Thumb-2 MLS: FB00 Rn | Ra Rd 0001 Rm
1874                // 11111011 0000 Rn | Ra Rd 0001 Rm
1875                let hw1: u16 = (0xFB00 | rn_bits) as u16;
1876                let hw2: u16 = ((ra_bits << 12) | (rd_bits << 8) | 0x10 | rm_bits) as u16;
1877
1878                let mut bytes = hw1.to_le_bytes().to_vec();
1879                bytes.extend_from_slice(&hw2.to_le_bytes());
1880                Ok(bytes)
1881            }
1882
1883            ArmOp::Mla { rd, rn, rm, ra } => {
1884                let rd_bits = reg_to_bits(rd);
1885                let rn_bits = reg_to_bits(rn);
1886                let rm_bits = reg_to_bits(rm);
1887                let ra_bits = reg_to_bits(ra);
1888
1889                // Thumb-2 MLA: FB00 Rn | Ra Rd 0000 Rm — same as MLS without the
1890                // bit-4 (0x10) op flag. rd = ra + rn*rm.
1891                let hw1: u16 = (0xFB00 | rn_bits) as u16;
1892                let hw2: u16 = ((ra_bits << 12) | (rd_bits << 8) | rm_bits) as u16;
1893
1894                let mut bytes = hw1.to_le_bytes().to_vec();
1895                bytes.extend_from_slice(&hw2.to_le_bytes());
1896                Ok(bytes)
1897            }
1898
1899            // AND (Thumb-2 32-bit)
1900            ArmOp::And { rd, rn, op2 } => {
1901                if let Operand2::Reg(rm) = op2 {
1902                    let rd_bits = reg_to_bits(rd);
1903                    let rn_bits = reg_to_bits(rn);
1904                    let rm_bits = reg_to_bits(rm);
1905
1906                    // Thumb-2 AND register: EA00 Rn | 0 Rd 00 00 Rm
1907                    let hw1: u16 = (0xEA00 | rn_bits) as u16;
1908                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1909
1910                    let mut bytes = hw1.to_le_bytes().to_vec();
1911                    bytes.extend_from_slice(&hw2.to_le_bytes());
1912                    Ok(bytes)
1913                } else if let Operand2::Imm(imm) = op2 {
1914                    let rd_bits = reg_to_bits(rd);
1915                    let rn_bits = reg_to_bits(rn);
1916                    let imm_val = *imm as u32;
1917
1918                    // Thumb-2 AND.W immediate T1: 11110 i 0 0000 S Rn | 0 imm3 Rd imm8
1919                    let i_bit = (imm_val >> 11) & 1;
1920                    let imm3 = (imm_val >> 8) & 0x7;
1921                    let imm8 = imm_val & 0xFF;
1922
1923                    let hw1: u16 = (0xF000 | (i_bit << 10) | rn_bits) as u16;
1924                    let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
1925
1926                    let mut bytes = hw1.to_le_bytes().to_vec();
1927                    bytes.extend_from_slice(&hw2.to_le_bytes());
1928                    Ok(bytes)
1929                } else {
1930                    // RegShift variant - fallback to NOP
1931                    let instr: u16 = 0xBF00;
1932                    Ok(instr.to_le_bytes().to_vec())
1933                }
1934            }
1935
1936            // ORR (Thumb-2 32-bit)
1937            ArmOp::Orr { rd, rn, op2 } => {
1938                if let Operand2::Reg(rm) = op2 {
1939                    let rd_bits = reg_to_bits(rd);
1940                    let rn_bits = reg_to_bits(rn);
1941                    let rm_bits = reg_to_bits(rm);
1942
1943                    // Thumb-2 ORR: EA40 Rn | 0 Rd 00 00 Rm
1944                    let hw1: u16 = (0xEA40 | rn_bits) as u16;
1945                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1946
1947                    let mut bytes = hw1.to_le_bytes().to_vec();
1948                    bytes.extend_from_slice(&hw2.to_le_bytes());
1949                    Ok(bytes)
1950                } else if let Operand2::Imm(imm) = op2 {
1951                    // ORR.W immediate T1: 11110 i 0 0010 S Rn | 0 imm3 Rd imm8.
1952                    // Only the zero-extended byte form (imm <= 0xFF) is encoded;
1953                    // larger modified immediates need ThumbExpandImm — return an
1954                    // error rather than silently emit a NOP (Ok-or-Err, #180/#185).
1955                    let imm_val = *imm as u32;
1956                    if imm_val > 0xFF {
1957                        return Err(synth_core::Error::synthesis(
1958                            "ORR immediate > 0xFF requires ThumbExpandImm (not yet implemented)",
1959                        ));
1960                    }
1961                    let rd_bits = reg_to_bits(rd);
1962                    let rn_bits = reg_to_bits(rn);
1963                    let hw1: u16 = (0xF040 | rn_bits) as u16;
1964                    let hw2: u16 = ((rd_bits << 8) | (imm_val & 0xFF)) as u16;
1965                    let mut bytes = hw1.to_le_bytes().to_vec();
1966                    bytes.extend_from_slice(&hw2.to_le_bytes());
1967                    Ok(bytes)
1968                } else {
1969                    let instr: u16 = 0xBF00;
1970                    Ok(instr.to_le_bytes().to_vec())
1971                }
1972            }
1973
1974            // EOR (Thumb-2 32-bit)
1975            ArmOp::Eor { rd, rn, op2 } => {
1976                if let Operand2::Reg(rm) = op2 {
1977                    let rd_bits = reg_to_bits(rd);
1978                    let rn_bits = reg_to_bits(rn);
1979                    let rm_bits = reg_to_bits(rm);
1980
1981                    // Thumb-2 EOR: EA80 Rn | 0 Rd 00 00 Rm
1982                    let hw1: u16 = (0xEA80 | rn_bits) as u16;
1983                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1984
1985                    let mut bytes = hw1.to_le_bytes().to_vec();
1986                    bytes.extend_from_slice(&hw2.to_le_bytes());
1987                    Ok(bytes)
1988                } else if let Operand2::Imm(imm) = op2 {
1989                    // EOR.W immediate T1: 11110 i 0 0100 S Rn | 0 imm3 Rd imm8.
1990                    // Byte form only (imm <= 0xFF); larger needs ThumbExpandImm —
1991                    // error, not a silent NOP (Ok-or-Err, #180/#185).
1992                    let imm_val = *imm as u32;
1993                    if imm_val > 0xFF {
1994                        return Err(synth_core::Error::synthesis(
1995                            "EOR immediate > 0xFF requires ThumbExpandImm (not yet implemented)",
1996                        ));
1997                    }
1998                    let rd_bits = reg_to_bits(rd);
1999                    let rn_bits = reg_to_bits(rn);
2000                    let hw1: u16 = (0xF080 | rn_bits) as u16;
2001                    let hw2: u16 = ((rd_bits << 8) | (imm_val & 0xFF)) as u16;
2002                    let mut bytes = hw1.to_le_bytes().to_vec();
2003                    bytes.extend_from_slice(&hw2.to_le_bytes());
2004                    Ok(bytes)
2005                } else {
2006                    let instr: u16 = 0xBF00;
2007                    Ok(instr.to_le_bytes().to_vec())
2008                }
2009            }
2010
2011            // Shift operations (16-bit for low registers)
2012            ArmOp::Lsl { rd, rn, shift } => {
2013                let rd_bits = reg_to_bits(rd) as u16;
2014                let rn_bits = reg_to_bits(rn) as u16;
2015                let shift_bits = (*shift as u16) & 0x1F;
2016
2017                if rd_bits < 8 && rn_bits < 8 {
2018                    // LSLS Rd, Rm, #imm5 (16-bit): 0000 0 imm5 Rm Rd
2019                    let instr: u16 = (shift_bits << 6) | (rn_bits << 3) | rd_bits;
2020                    Ok(instr.to_le_bytes().to_vec())
2021                } else {
2022                    // Use 32-bit encoding for high registers
2023                    self.encode_thumb32_shift(rd, rn, *shift, 0b00) // LSL type
2024                }
2025            }
2026
2027            ArmOp::Lsr { rd, rn, shift } => {
2028                let rd_bits = reg_to_bits(rd) as u16;
2029                let rn_bits = reg_to_bits(rn) as u16;
2030                let shift_bits = (*shift as u16) & 0x1F;
2031
2032                if rd_bits < 8 && rn_bits < 8 && shift_bits > 0 {
2033                    // LSRS Rd, Rm, #imm5 (16-bit): 0000 1 imm5 Rm Rd
2034                    let instr: u16 = 0x0800 | (shift_bits << 6) | (rn_bits << 3) | rd_bits;
2035                    Ok(instr.to_le_bytes().to_vec())
2036                } else {
2037                    self.encode_thumb32_shift(rd, rn, *shift, 0b01) // LSR type
2038                }
2039            }
2040
2041            ArmOp::Asr { rd, rn, shift } => {
2042                let rd_bits = reg_to_bits(rd) as u16;
2043                let rn_bits = reg_to_bits(rn) as u16;
2044                let shift_bits = (*shift as u16) & 0x1F;
2045
2046                if rd_bits < 8 && rn_bits < 8 && shift_bits > 0 {
2047                    // ASRS Rd, Rm, #imm5 (16-bit): 0001 0 imm5 Rm Rd
2048                    let instr: u16 = 0x1000 | (shift_bits << 6) | (rn_bits << 3) | rd_bits;
2049                    Ok(instr.to_le_bytes().to_vec())
2050                } else {
2051                    self.encode_thumb32_shift(rd, rn, *shift, 0b10) // ASR type
2052                }
2053            }
2054
2055            ArmOp::Ror { rd, rn, shift } => {
2056                // ROR doesn't have a 16-bit immediate form, use 32-bit
2057                self.encode_thumb32_shift(rd, rn, *shift, 0b11) // ROR type
2058            }
2059
2060            // Register-based shifts (Thumb-2 32-bit)
2061            // Encoding: 11111010 0xxS Rn 1111 Rd 0000 Rm
2062            // xx = shift type: 00=LSL, 01=LSR, 10=ASR, 11=ROR
2063            ArmOp::LslReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b00),
2064            ArmOp::LsrReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b01),
2065            ArmOp::AsrReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b10),
2066            ArmOp::RorReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b11),
2067
2068            // RSB (Reverse Subtract): Rd = imm - Rn
2069            // Thumb-2 T2 encoding: 11110 i 0 1110 S Rn | 0 imm3 Rd imm8
2070            ArmOp::Rsb { rd, rn, imm } => {
2071                let rd_bits = reg_to_bits(rd);
2072                let rn_bits = reg_to_bits(rn);
2073                let imm_val = *imm;
2074
2075                let i_bit = (imm_val >> 11) & 1;
2076                let imm3 = (imm_val >> 8) & 0x7;
2077                let imm8 = imm_val & 0xFF;
2078
2079                // hw1: 11110 i 01110 0 Rn  (S=0)
2080                let hw1: u16 = (0xF1C0 | (i_bit << 10) | rn_bits) as u16;
2081                // hw2: 0 imm3 Rd imm8
2082                let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
2083
2084                let mut bytes = hw1.to_le_bytes().to_vec();
2085                bytes.extend_from_slice(&hw2.to_le_bytes());
2086                Ok(bytes)
2087            }
2088
2089            // CLZ (Thumb-2 32-bit)
2090            ArmOp::Clz { rd, rm } => {
2091                let rd_bits = reg_to_bits(rd);
2092                let rm_bits = reg_to_bits(rm);
2093
2094                // Thumb-2 CLZ: FAB0 Rm | F8 Rd Rm
2095                // 11111010 1011 Rm | 1111 1000 Rd Rm
2096                let hw1: u16 = (0xFAB0 | rm_bits) as u16;
2097                let hw2: u16 = (0xF080 | (rd_bits << 8) | rm_bits) as u16;
2098
2099                let mut bytes = hw1.to_le_bytes().to_vec();
2100                bytes.extend_from_slice(&hw2.to_le_bytes());
2101                Ok(bytes)
2102            }
2103
2104            // RBIT (Thumb-2 32-bit)
2105            ArmOp::Rbit { rd, rm } => {
2106                let rd_bits = reg_to_bits(rd);
2107                let rm_bits = reg_to_bits(rm);
2108
2109                // Thumb-2 RBIT: FA90 Rm | F0 Rd A0 Rm
2110                // 11111010 1001 Rm | 1111 Rd 1010 Rm
2111                let hw1: u16 = (0xFA90 | rm_bits) as u16;
2112                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rm_bits) as u16;
2113
2114                let mut bytes = hw1.to_le_bytes().to_vec();
2115                bytes.extend_from_slice(&hw2.to_le_bytes());
2116                Ok(bytes)
2117            }
2118
2119            // SXTB (16-bit for low registers)
2120            ArmOp::Sxtb { rd, rm } => {
2121                let rd_bits = reg_to_bits(rd) as u16;
2122                let rm_bits = reg_to_bits(rm) as u16;
2123
2124                if rd_bits < 8 && rm_bits < 8 {
2125                    // SXTB Rd, Rm (16-bit): 1011 0010 01 Rm Rd
2126                    let instr: u16 = 0xB240 | (rm_bits << 3) | rd_bits;
2127                    Ok(instr.to_le_bytes().to_vec())
2128                } else {
2129                    // Thumb-2 SXTB.W: FA4F F(rd)80 (rm)
2130                    // 11111010 0100 1111 | 1111 Rd 10 rotate Rm
2131                    let rd_bits32 = rd_bits as u32;
2132                    let rm_bits32 = rm_bits as u32;
2133                    let hw1: u16 = 0xFA4F;
2134                    let hw2: u16 = (0xF080 | (rd_bits32 << 8) | rm_bits32) as u16;
2135                    let mut bytes = hw1.to_le_bytes().to_vec();
2136                    bytes.extend_from_slice(&hw2.to_le_bytes());
2137                    Ok(bytes)
2138                }
2139            }
2140
2141            // SXTH (16-bit for low registers)
2142            ArmOp::Sxth { rd, rm } => {
2143                let rd_bits = reg_to_bits(rd) as u16;
2144                let rm_bits = reg_to_bits(rm) as u16;
2145
2146                if rd_bits < 8 && rm_bits < 8 {
2147                    // SXTH Rd, Rm (16-bit): 1011 0010 00 Rm Rd
2148                    let instr: u16 = 0xB200 | (rm_bits << 3) | rd_bits;
2149                    Ok(instr.to_le_bytes().to_vec())
2150                } else {
2151                    // Thumb-2 SXTH.W: FA0F F(rd)80 (rm)
2152                    // 11111010 0000 1111 | 1111 Rd 10 rotate Rm
2153                    let rd_bits32 = rd_bits as u32;
2154                    let rm_bits32 = rm_bits as u32;
2155                    let hw1: u16 = 0xFA0F;
2156                    let hw2: u16 = (0xF080 | (rd_bits32 << 8) | rm_bits32) as u16;
2157                    let mut bytes = hw1.to_le_bytes().to_vec();
2158                    bytes.extend_from_slice(&hw2.to_le_bytes());
2159                    Ok(bytes)
2160                }
2161            }
2162
2163            // CMP (can be 16-bit for low registers)
2164            ArmOp::Cmp { rn, op2 } => {
2165                let rn_bits = reg_to_bits(rn) as u16;
2166
2167                if let Operand2::Imm(imm) = op2 {
2168                    // Only use 16-bit encoding for non-negative immediates 0-255
2169                    // Negative immediates must use 32-bit encoding
2170                    if *imm >= 0 && *imm <= 255 && rn_bits < 8 {
2171                        // CMP Rn, #imm8 (16-bit): 0010 1 Rn imm8
2172                        let instr: u16 = 0x2800 | (rn_bits << 8) | (*imm as u16 & 0xFF);
2173                        Ok(instr.to_le_bytes().to_vec())
2174                    } else {
2175                        self.encode_thumb32_cmp_imm(rn, *imm as u32)
2176                    }
2177                } else if let Operand2::Reg(rm) = op2 {
2178                    let rm_bits = reg_to_bits(rm) as u16;
2179                    if rn_bits < 8 && rm_bits < 8 {
2180                        // CMP Rn, Rm (16-bit low): 0100 0010 10 Rm Rn
2181                        let instr: u16 = 0x4280 | (rm_bits << 3) | rn_bits;
2182                        Ok(instr.to_le_bytes().to_vec())
2183                    } else {
2184                        // CMP Rn, Rm (16-bit high): 0100 0101 N Rm Rn[2:0]
2185                        let n_bit = (rn_bits >> 3) & 1;
2186                        let instr: u16 = 0x4500 | (n_bit << 7) | (rm_bits << 3) | (rn_bits & 0x7);
2187                        Ok(instr.to_le_bytes().to_vec())
2188                    }
2189                } else {
2190                    let instr: u16 = 0xBF00;
2191                    Ok(instr.to_le_bytes().to_vec())
2192                }
2193            }
2194
2195            // CMN (Compare Negative) - computes Rn + op2 and sets flags
2196            // CMN Rn, #1 sets Z flag if Rn == -1 (since -1 + 1 = 0)
2197            ArmOp::Cmn { rn, op2 } => {
2198                let rn_bits = reg_to_bits(rn) as u16;
2199
2200                if let Operand2::Imm(imm) = op2 {
2201                    // CMN.W Rn, #imm (32-bit encoding)
2202                    // Encoding: F110 Rn | 0F00 imm8 (for small immediates 0-255)
2203                    if *imm >= 0 && *imm <= 255 {
2204                        let imm8 = *imm as u16 & 0xFF;
2205                        let hw1: u16 = 0xF110 | rn_bits;
2206                        let hw2: u16 = 0x0F00 | imm8;
2207                        let mut bytes = hw1.to_le_bytes().to_vec();
2208                        bytes.extend_from_slice(&hw2.to_le_bytes());
2209                        Ok(bytes)
2210                    } else {
2211                        // For other immediates, fallback to NOP (should not happen in our use case)
2212                        Ok(vec![0xBF, 0x00])
2213                    }
2214                } else if let Operand2::Reg(rm) = op2 {
2215                    let rm_bits = reg_to_bits(rm) as u16;
2216                    // 16-bit CMN (T1) only encodes R0-R7; high registers overflow
2217                    // the 3-bit fields and corrupt the operands (#184, the #180
2218                    // class). CMN has no high-register 16-bit form, so fall back
2219                    // to 32-bit CMN.W (T2): EB10 Rn | 0F00 Rm (ADD.W with S=1 and
2220                    // Rd discarded as PC/1111).
2221                    if rn_bits < 8 && rm_bits < 8 {
2222                        // CMN Rn, Rm (16-bit): 0100 0010 11 Rm Rn
2223                        let instr: u16 = 0x42C0 | (rm_bits << 3) | rn_bits;
2224                        Ok(instr.to_le_bytes().to_vec())
2225                    } else {
2226                        let hw1: u16 = 0xEB10 | rn_bits;
2227                        let hw2: u16 = 0x0F00 | rm_bits;
2228                        let mut bytes = hw1.to_le_bytes().to_vec();
2229                        bytes.extend_from_slice(&hw2.to_le_bytes());
2230                        Ok(bytes)
2231                    }
2232                } else {
2233                    Ok(vec![0xBF, 0x00])
2234                }
2235            }
2236
2237            // LDR (can be 16-bit for simple cases)
2238            ArmOp::Ldr { rd, addr } => {
2239                let rd_bits = reg_to_bits(rd);
2240                let base_bits = reg_to_bits(&addr.base);
2241
2242                // Handle register offset mode [base, Roff] or [base, Roff, #imm]
2243                if let Some(offset_reg) = &addr.offset_reg {
2244                    let rm_bits = reg_to_bits(offset_reg);
2245
2246                    // If there's also an immediate offset, we need to ADD it first
2247                    if addr.offset != 0 {
2248                        // Use R12 (IP) as scratch to avoid clobbering the address register
2249                        // ADD R12, Rm, #offset; LDR Rd, [base, R12]
2250                        let scratch = Reg::R12;
2251                        let mut bytes =
2252                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2253                        bytes.extend(self.encode_thumb32_ldr_reg(rd, &addr.base, &scratch)?);
2254                        return Ok(bytes);
2255                    }
2256
2257                    // Simple register offset: LDR Rd, [Rn, Rm]
2258                    // 16-bit: only if Rd, Rn, Rm < R8
2259                    if rd_bits < 8 && base_bits < 8 && rm_bits < 8 {
2260                        // LDR Rd, [Rn, Rm] (16-bit): 0101 100 Rm Rn Rd
2261                        let instr: u16 = 0x5800
2262                            | ((rm_bits as u16) << 6)
2263                            | ((base_bits as u16) << 3)
2264                            | (rd_bits as u16);
2265                        return Ok(instr.to_le_bytes().to_vec());
2266                    }
2267
2268                    // 32-bit register offset
2269                    return self.encode_thumb32_ldr_reg(rd, &addr.base, offset_reg);
2270                }
2271
2272                // Immediate offset mode [base, #imm]
2273                let offset = addr.offset as u32;
2274
2275                if rd_bits < 8 && base_bits < 8 && (offset & 0x3) == 0 && offset <= 124 {
2276                    // LDR Rd, [Rn, #imm5*4] (16-bit): 0110 1 imm5 Rn Rd
2277                    let imm5 = (offset >> 2) as u16;
2278                    let instr: u16 =
2279                        0x6800 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2280                    Ok(instr.to_le_bytes().to_vec())
2281                } else {
2282                    self.encode_thumb32_ldr(rd, &addr.base, offset)
2283                }
2284            }
2285
2286            // STR (can be 16-bit for simple cases)
2287            ArmOp::Str { rd, addr } => {
2288                let rd_bits = reg_to_bits(rd);
2289                let base_bits = reg_to_bits(&addr.base);
2290
2291                // Handle register offset mode [base, Roff] or [base, Roff, #imm]
2292                if let Some(offset_reg) = &addr.offset_reg {
2293                    let rm_bits = reg_to_bits(offset_reg);
2294
2295                    // If there's also an immediate offset, we need to ADD it first
2296                    if addr.offset != 0 {
2297                        // Use R12 (IP) as scratch to avoid clobbering the address register
2298                        // ADD R12, Rm, #offset; STR Rd, [base, R12]
2299                        let scratch = Reg::R12;
2300                        let mut bytes =
2301                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2302                        bytes.extend(self.encode_thumb32_str_reg(rd, &addr.base, &scratch)?);
2303                        return Ok(bytes);
2304                    }
2305
2306                    // Simple register offset: STR Rd, [Rn, Rm]
2307                    // 16-bit: only if Rd, Rn, Rm < R8
2308                    if rd_bits < 8 && base_bits < 8 && rm_bits < 8 {
2309                        // STR Rd, [Rn, Rm] (16-bit): 0101 000 Rm Rn Rd
2310                        let instr: u16 = 0x5000
2311                            | ((rm_bits as u16) << 6)
2312                            | ((base_bits as u16) << 3)
2313                            | (rd_bits as u16);
2314                        return Ok(instr.to_le_bytes().to_vec());
2315                    }
2316
2317                    // 32-bit register offset
2318                    return self.encode_thumb32_str_reg(rd, &addr.base, offset_reg);
2319                }
2320
2321                // Immediate offset mode [base, #imm]
2322                let offset = addr.offset as u32;
2323
2324                if rd_bits < 8 && base_bits < 8 && (offset & 0x3) == 0 && offset <= 124 {
2325                    // STR Rd, [Rn, #imm5*4] (16-bit): 0110 0 imm5 Rn Rd
2326                    let imm5 = (offset >> 2) as u16;
2327                    let instr: u16 =
2328                        0x6000 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2329                    Ok(instr.to_le_bytes().to_vec())
2330                } else {
2331                    self.encode_thumb32_str(rd, &addr.base, offset)
2332                }
2333            }
2334
2335            // LDRB (Thumb-2)
2336            ArmOp::Ldrb { rd, addr } => {
2337                let rd_bits = reg_to_bits(rd);
2338                let base_bits = reg_to_bits(&addr.base);
2339
2340                if let Some(offset_reg) = &addr.offset_reg {
2341                    if addr.offset != 0 {
2342                        let scratch = Reg::R12;
2343                        let mut bytes =
2344                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2345                        bytes.extend(self.encode_thumb32_ldrb_reg(rd, &addr.base, &scratch)?);
2346                        return Ok(bytes);
2347                    }
2348                    return self.encode_thumb32_ldrb_reg(rd, &addr.base, offset_reg);
2349                }
2350
2351                let offset = addr.offset as u32;
2352                if rd_bits < 8 && base_bits < 8 && offset <= 31 {
2353                    // LDRB Rd, [Rn, #imm5] (16-bit): 0111 1 imm5 Rn Rd
2354                    let instr: u16 = 0x7800
2355                        | ((offset as u16) << 6)
2356                        | ((base_bits as u16) << 3)
2357                        | (rd_bits as u16);
2358                    Ok(instr.to_le_bytes().to_vec())
2359                } else {
2360                    self.encode_thumb32_ldrb_imm(rd, &addr.base, offset)
2361                }
2362            }
2363
2364            // LDRSB (Thumb-2)
2365            ArmOp::Ldrsb { rd, addr } => {
2366                let rd_bits = reg_to_bits(rd);
2367                let base_bits = reg_to_bits(&addr.base);
2368
2369                if let Some(offset_reg) = &addr.offset_reg {
2370                    if addr.offset != 0 {
2371                        let scratch = Reg::R12;
2372                        let mut bytes =
2373                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2374                        bytes.extend(self.encode_thumb32_ldrsb_reg(rd, &addr.base, &scratch)?);
2375                        return Ok(bytes);
2376                    }
2377                    return self.encode_thumb32_ldrsb_reg(rd, &addr.base, offset_reg);
2378                }
2379
2380                let offset = addr.offset as u32;
2381                // LDRSB has no 16-bit immediate form (only register)
2382                // For 16-bit reg form: only if Rd, Rn, Rm < R8
2383                if rd_bits < 8 && base_bits < 8 && offset == 0 {
2384                    // No immediate 16-bit encoding for LDRSB; use 32-bit
2385                    self.encode_thumb32_ldrsb_imm(rd, &addr.base, offset)
2386                } else {
2387                    self.encode_thumb32_ldrsb_imm(rd, &addr.base, offset)
2388                }
2389            }
2390
2391            // LDRH (Thumb-2)
2392            ArmOp::Ldrh { rd, addr } => {
2393                let rd_bits = reg_to_bits(rd);
2394                let base_bits = reg_to_bits(&addr.base);
2395
2396                if let Some(offset_reg) = &addr.offset_reg {
2397                    if addr.offset != 0 {
2398                        let scratch = Reg::R12;
2399                        let mut bytes =
2400                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2401                        bytes.extend(self.encode_thumb32_ldrh_reg(rd, &addr.base, &scratch)?);
2402                        return Ok(bytes);
2403                    }
2404                    return self.encode_thumb32_ldrh_reg(rd, &addr.base, offset_reg);
2405                }
2406
2407                let offset = addr.offset as u32;
2408                if rd_bits < 8 && base_bits < 8 && (offset & 0x1) == 0 && offset <= 62 {
2409                    // LDRH Rd, [Rn, #imm5*2] (16-bit): 1000 1 imm5 Rn Rd
2410                    let imm5 = (offset >> 1) as u16;
2411                    let instr: u16 =
2412                        0x8800 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2413                    Ok(instr.to_le_bytes().to_vec())
2414                } else {
2415                    self.encode_thumb32_ldrh_imm(rd, &addr.base, offset)
2416                }
2417            }
2418
2419            // LDRSH (Thumb-2)
2420            ArmOp::Ldrsh { rd, addr } => {
2421                if let Some(offset_reg) = &addr.offset_reg {
2422                    if addr.offset != 0 {
2423                        let scratch = Reg::R12;
2424                        let mut bytes =
2425                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2426                        bytes.extend(self.encode_thumb32_ldrsh_reg(rd, &addr.base, &scratch)?);
2427                        return Ok(bytes);
2428                    }
2429                    return self.encode_thumb32_ldrsh_reg(rd, &addr.base, offset_reg);
2430                }
2431
2432                let offset = addr.offset as u32;
2433                self.encode_thumb32_ldrsh_imm(rd, &addr.base, offset)
2434            }
2435
2436            // STRB (Thumb-2)
2437            ArmOp::Strb { rd, addr } => {
2438                let rd_bits = reg_to_bits(rd);
2439                let base_bits = reg_to_bits(&addr.base);
2440
2441                if let Some(offset_reg) = &addr.offset_reg {
2442                    if addr.offset != 0 {
2443                        let scratch = Reg::R12;
2444                        let mut bytes =
2445                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2446                        bytes.extend(self.encode_thumb32_strb_reg(rd, &addr.base, &scratch)?);
2447                        return Ok(bytes);
2448                    }
2449                    return self.encode_thumb32_strb_reg(rd, &addr.base, offset_reg);
2450                }
2451
2452                let offset = addr.offset as u32;
2453                if rd_bits < 8 && base_bits < 8 && offset <= 31 {
2454                    // STRB Rd, [Rn, #imm5] (16-bit): 0111 0 imm5 Rn Rd
2455                    let instr: u16 = 0x7000
2456                        | ((offset as u16) << 6)
2457                        | ((base_bits as u16) << 3)
2458                        | (rd_bits as u16);
2459                    Ok(instr.to_le_bytes().to_vec())
2460                } else {
2461                    self.encode_thumb32_strb_imm(rd, &addr.base, offset)
2462                }
2463            }
2464
2465            // STRH (Thumb-2)
2466            ArmOp::Strh { rd, addr } => {
2467                let rd_bits = reg_to_bits(rd);
2468                let base_bits = reg_to_bits(&addr.base);
2469
2470                if let Some(offset_reg) = &addr.offset_reg {
2471                    if addr.offset != 0 {
2472                        let scratch = Reg::R12;
2473                        let mut bytes =
2474                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2475                        bytes.extend(self.encode_thumb32_strh_reg(rd, &addr.base, &scratch)?);
2476                        return Ok(bytes);
2477                    }
2478                    return self.encode_thumb32_strh_reg(rd, &addr.base, offset_reg);
2479                }
2480
2481                let offset = addr.offset as u32;
2482                if rd_bits < 8 && base_bits < 8 && (offset & 0x1) == 0 && offset <= 62 {
2483                    // STRH Rd, [Rn, #imm5*2] (16-bit): 1000 0 imm5 Rn Rd
2484                    let imm5 = (offset >> 1) as u16;
2485                    let instr: u16 =
2486                        0x8000 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2487                    Ok(instr.to_le_bytes().to_vec())
2488                } else {
2489                    self.encode_thumb32_strh_imm(rd, &addr.base, offset)
2490                }
2491            }
2492
2493            // MemorySize (Thumb-2)
2494            ArmOp::MemorySize { rd } => {
2495                // LSR rd, R10, #16 — memory size in bytes / 65536 = pages
2496                // Thumb-2 16-bit: LSRS Rd, Rm, #imm5 — 0000 1 imm5 Rm Rd
2497                let rd_bits = reg_to_bits(rd);
2498                let r10_bits = reg_to_bits(&Reg::R10);
2499                if rd_bits < 8 && r10_bits < 8 {
2500                    let instr: u16 =
2501                        0x0800 | (16u16 << 6) | ((r10_bits as u16) << 3) | (rd_bits as u16);
2502                    Ok(instr.to_le_bytes().to_vec())
2503                } else {
2504                    // Thumb-2 32-bit LSR: 1110 1010 010 0 1111 | 0 imm3 Rd imm2 01 Rm
2505                    let imm5: u32 = 16;
2506                    let imm3 = (imm5 >> 2) & 0x7;
2507                    let imm2 = imm5 & 0x3;
2508                    let hw1: u16 = 0xEA4F;
2509                    let hw2: u16 =
2510                        ((imm3 << 12) | (rd_bits << 8) | (imm2 << 6) | 0x10 | r10_bits) as u16;
2511                    let mut bytes = hw1.to_le_bytes().to_vec();
2512                    bytes.extend_from_slice(&hw2.to_le_bytes());
2513                    Ok(bytes)
2514                }
2515            }
2516
2517            // MemoryGrow (Thumb-2)
2518            ArmOp::MemoryGrow { rd, .. } => {
2519                // On embedded with fixed memory, always return -1 (failure)
2520                // MVN rd, #0 → MOV rd, #-1
2521                // Thumb-2 32-bit: MVN: 1111 0 i 0 0 0 1 1 0 1111 | 0 imm3 Rd imm8
2522                let rd_bits = reg_to_bits(rd);
2523                let hw1: u16 = 0xF06F; // MVN with i=0
2524                let hw2: u16 = (rd_bits << 8) as u16; // imm8=0 → ~0 = 0xFFFFFFFF = -1
2525                let mut bytes = hw1.to_le_bytes().to_vec();
2526                bytes.extend_from_slice(&hw2.to_le_bytes());
2527                Ok(bytes)
2528            }
2529
2530            // BX (16-bit)
2531            ArmOp::Bx { rm } => {
2532                let rm_bits = reg_to_bits(rm) as u16;
2533                // BX Rm (16-bit): 0100 0111 0 Rm 000
2534                let instr: u16 = 0x4700 | (rm_bits << 3);
2535                Ok(instr.to_le_bytes().to_vec())
2536            }
2537
2538            // BLX (16-bit) - Branch with Link and Exchange
2539            // BLX Rm: 0100 0111 1 Rm 000
2540            ArmOp::Blx { rm } => {
2541                let rm_bits = reg_to_bits(rm) as u16;
2542                let instr: u16 = 0x4780 | (rm_bits << 3);
2543                Ok(instr.to_le_bytes().to_vec())
2544            }
2545
2546            // CallIndirect - indirect function call via table lookup
2547            // table_index_reg contains the table index
2548            // Generates: LSL R12, idx, #2; LDR R12, [R12, table_base]; BLX R12
2549            ArmOp::CallIndirect {
2550                rd: _,
2551                type_idx: _,
2552                table_index_reg,
2553            } => {
2554                let idx_reg = reg_to_bits(table_index_reg);
2555                let mut bytes = Vec::new();
2556
2557                // For now, we generate code that:
2558                // 1. Multiplies index by 4 (function pointer size)
2559                // 2. Loads function pointer from table (assumes table base in R11)
2560                // 3. Calls the function via BLX
2561                //
2562                // Table base setup must be done by caller/runtime.
2563                // This is a simplified implementation - full support needs:
2564                // - Table base address resolution
2565                // - Type signature checking
2566                // - Bounds checking
2567
2568                // LSL R12, idx_reg, #2 (multiply index by 4)
2569                // Thumb-2 MOV with shift: 11101010 010 S 1111 | 0 imm3 Rd imm2 type Rm
2570                // LSL: type=00, imm5=2 -> imm3=0, imm2=10
2571                let hw1: u16 = 0xEA4F_u16; // MOV.W R12, Rm, LSL #2
2572                let hw2: u16 = ((0x0C00 | (0b10 << 4)) | idx_reg) as u16;
2573                bytes.extend_from_slice(&hw1.to_le_bytes());
2574                bytes.extend_from_slice(&hw2.to_le_bytes());
2575
2576                // LDR R12, [R11, R12] - load function pointer
2577                // Thumb-2 LDR (register): 1111 1000 0101 Rn | Rt 0000 00 imm2 Rm
2578                // Rn=R11, Rt=R12, Rm=R12, imm2=00 (no shift)
2579                let ldr_hw1: u16 = 0xF85B; // LDR.W Rt, [R11, Rm]
2580                let ldr_hw2: u16 = 0xC00C; // Rt=R12, imm2=00, Rm=R12
2581                bytes.extend_from_slice(&ldr_hw1.to_le_bytes());
2582                bytes.extend_from_slice(&ldr_hw2.to_le_bytes());
2583
2584                // BLX R12 (call function indirectly)
2585                // BLX Rm (16-bit): 0100 0111 1 Rm 000
2586                let blx: u16 = 0x47E0; // BLX R12
2587                bytes.extend_from_slice(&blx.to_le_bytes());
2588
2589                Ok(bytes)
2590            }
2591
2592            // Label pseudo-instruction: emits no machine code
2593            ArmOp::Label { .. } => Ok(Vec::new()),
2594
2595            // Conditional branch to label (generic) - offset 0, will be patched
2596            ArmOp::Bcc { cond, label: _ } => {
2597                use synth_synthesis::Condition;
2598                let cond_bits: u16 = match cond {
2599                    Condition::EQ => 0x0,
2600                    Condition::NE => 0x1,
2601                    Condition::HS => 0x2,
2602                    Condition::LO => 0x3,
2603                    Condition::HI => 0x8,
2604                    Condition::LS => 0x9,
2605                    Condition::GE => 0xA,
2606                    Condition::LT => 0xB,
2607                    Condition::GT => 0xC,
2608                    Condition::LE => 0xD,
2609                };
2610                // 16-bit B<cond> with offset 0: 1101 cond imm8
2611                let instr: u16 = 0xD000 | (cond_bits << 8);
2612                Ok(instr.to_le_bytes().to_vec())
2613            }
2614
2615            // Branch instructions
2616            ArmOp::B { label: _ } => {
2617                // Simplified: B.N with offset 0
2618                // For real usage, would need label resolution
2619                let instr: u16 = 0xE000; // B.N #0
2620                Ok(instr.to_le_bytes().to_vec())
2621            }
2622
2623            // BHS (Branch if Higher or Same) - used for bounds checking
2624            // Condition code: 0x2 (C set)
2625            ArmOp::Bhs { label: _ } => {
2626                // 16-bit B<cond> with offset 0: 1101 cond imm8
2627                // cond = 0x2 (HS)
2628                let instr: u16 = 0xD200; // BHS.N #0
2629                Ok(instr.to_le_bytes().to_vec())
2630            }
2631
2632            // BLO (Branch if Lower) - complementary to BHS
2633            // Condition code: 0x3 (C clear)
2634            ArmOp::Blo { label: _ } => {
2635                // 16-bit B<cond> with offset 0: 1101 cond imm8
2636                // cond = 0x3 (LO)
2637                let instr: u16 = 0xD300; // BLO.N #0
2638                Ok(instr.to_le_bytes().to_vec())
2639            }
2640
2641            // Branch with numeric offset (Thumb-2)
2642            // Thumb-2 B.W instruction: 32-bit with +-16MB range
2643            ArmOp::BOffset { offset } => {
2644                // offset is already the halfword displacement: (target - branch - 4) / 2
2645                // This is the raw encoded value, accounting for variable-length instructions
2646                let halfword_offset = *offset;
2647
2648                // 16-bit B.N encoding: 1110 0 imm11 (11-bit signed halfword offset)
2649                // Range: -1024 to +1022 halfwords
2650                if (-1024..=1022).contains(&halfword_offset) {
2651                    // 16-bit B.N encoding: 1110 0 imm11
2652                    let imm11 = (halfword_offset as u16) & 0x7FF;
2653                    let instr: u16 = 0xE000 | imm11;
2654                    Ok(instr.to_le_bytes().to_vec())
2655                } else {
2656                    // 32-bit B.W encoding for larger offsets
2657                    // First halfword: 1111 0 S imm10
2658                    // Second halfword: 10 J1 0 J2 imm11
2659                    // Total offset = SignExtend(S:I1:I2:imm10:imm11:0)
2660                    // where I1 = NOT(J1 XOR S), I2 = NOT(J2 XOR S)
2661
2662                    // The B.W (T4) encoding packs the signed offset as:
2663                    //   S:I1:I2:imm10:imm11:0  (25-bit signed, halfword-aligned)
2664                    // where J1 = NOT(I1 XOR S), J2 = NOT(I2 XOR S)
2665                    // Input halfword_offset already equals (target - PC - 4) / 2,
2666                    // so the full byte offset = halfword_offset << 1.
2667                    // The encoding fields split that 25-bit signed value (including the
2668                    // implicit trailing zero) as: S | imm10 | imm11
2669                    // with I1 = bit 23 and I2 = bit 22 of the signed offset.
2670                    let signed_offset = halfword_offset << 1; // byte offset
2671                    let s = if signed_offset < 0 { 1u32 } else { 0u32 };
2672                    let uoffset = signed_offset as u32;
2673                    let imm10 = (uoffset >> 12) & 0x3FF; // bits [21:12]
2674                    let imm11 = (uoffset >> 1) & 0x7FF; // bits [11:1]
2675                    let i1 = (uoffset >> 23) & 1; // bit 23
2676                    let i2 = (uoffset >> 22) & 1; // bit 22
2677                    let j1 = (!(i1 ^ s)) & 1; // J1 = NOT(I1 XOR S)
2678                    let j2 = (!(i2 ^ s)) & 1; // J2 = NOT(I2 XOR S)
2679
2680                    let hw1: u16 = (0xF000 | (s << 10) | imm10) as u16;
2681                    let hw2: u16 = (0x9000 | (j1 << 13) | (j2 << 11) | imm11) as u16;
2682
2683                    let mut bytes = hw1.to_le_bytes().to_vec();
2684                    bytes.extend_from_slice(&hw2.to_le_bytes());
2685                    Ok(bytes)
2686                }
2687            }
2688
2689            // Conditional branch with numeric offset (Thumb-2)
2690            ArmOp::BCondOffset { cond, offset } => {
2691                use synth_synthesis::Condition;
2692                let cond_bits: u16 = match cond {
2693                    Condition::EQ => 0x0,
2694                    Condition::NE => 0x1,
2695                    Condition::HS => 0x2,
2696                    Condition::LO => 0x3,
2697                    Condition::HI => 0x8,
2698                    Condition::LS => 0x9,
2699                    Condition::GE => 0xA,
2700                    Condition::LT => 0xB,
2701                    Condition::GT => 0xC,
2702                    Condition::LE => 0xD,
2703                };
2704
2705                // offset is already the halfword displacement: (target - branch - 4) / 2
2706                // This is the raw imm8 value for 16-bit B<cond> encoding
2707                let halfword_offset = *offset;
2708
2709                // 16-bit B<cond> encoding: 1101 cond imm8
2710                // Range: -256 to +254 halfwords (imm8 is sign-extended and shifted left 1)
2711                if (-128..=127).contains(&halfword_offset) {
2712                    let imm8 = (halfword_offset as u16) & 0xFF;
2713                    let instr: u16 = 0xD000 | (cond_bits << 8) | imm8;
2714                    Ok(instr.to_le_bytes().to_vec())
2715                } else {
2716                    // 32-bit B<cond>.W for larger offsets
2717                    // First halfword: 1111 0 S cond imm6
2718                    // Second halfword: 10 J1 0 J2 imm11
2719                    let offset = halfword_offset >> 1;
2720                    let s = if offset < 0 { 1u32 } else { 0u32 };
2721                    let imm6 = ((offset >> 11) as u32) & 0x3F;
2722                    let imm11 = (offset as u32) & 0x7FF;
2723                    let j1 = if s == 1 { 1 } else { 0 };
2724                    let j2 = if s == 1 { 1 } else { 0 };
2725
2726                    let hw1: u16 = (0xF000 | (s << 10) | ((cond_bits as u32) << 6) | imm6) as u16;
2727                    let hw2: u16 = (0x8000 | (j1 << 13) | (j2 << 11) | imm11) as u16;
2728
2729                    let mut bytes = hw1.to_le_bytes().to_vec();
2730                    bytes.extend_from_slice(&hw2.to_le_bytes());
2731                    Ok(bytes)
2732                }
2733            }
2734
2735            ArmOp::Bl { label: _ } => {
2736                // BL is always 32-bit in Thumb-2, encoded here as a relocatable
2737                // placeholder; an R_ARM_THM_CALL relocation patches the target
2738                // (see arm_backend.rs). The placeholder must carry an embedded
2739                // addend of -4 so the relocation nets to exactly the symbol S.
2740                //
2741                // Thumb BL computes `target = (P + 4) + signed_offset`. Under
2742                // R_ARM_THM_CALL the linker resolves using the in-place addend;
2743                // a 0xF800 placeholder (addend 0) lands at S+4 — every call one
2744                // instruction past the callee entry (#174). The correct
2745                // placeholder is what `gas` emits for `bl <extern>`:
2746                //   f7ff fffe  ->  `bl <self>`  (S=1, J1=J2=1, imm = -4 addend),
2747                // i.e. hw1=0xF7FF, hw2=0xFFFE. This nets to S, not S+4.
2748                // (The earlier 0xD000 was worse still — a ~+0x600000 addend,
2749                // the garbage `bl c0000c` and "truncated to fit" of #167.)
2750                let hw1: u16 = 0xF7FF;
2751                let hw2: u16 = 0xFFFE;
2752                let mut bytes = hw1.to_le_bytes().to_vec();
2753                bytes.extend_from_slice(&hw2.to_le_bytes());
2754                Ok(bytes)
2755            }
2756
2757            // MVN
2758            ArmOp::Mvn { rd, op2 } => {
2759                if let Operand2::Reg(rm) = op2 {
2760                    let rd_bits = reg_to_bits(rd) as u16;
2761                    let rm_bits = reg_to_bits(rm) as u16;
2762
2763                    if rd_bits < 8 && rm_bits < 8 {
2764                        // MVNS Rd, Rm (16-bit): 0100 0011 11 Rm Rd
2765                        let instr: u16 = 0x43C0 | (rm_bits << 3) | rd_bits;
2766                        Ok(instr.to_le_bytes().to_vec())
2767                    } else {
2768                        // 32-bit MVN
2769                        let hw1: u16 = 0xEA6F_u16;
2770                        let hw2: u16 = ((reg_to_bits(rd) << 8) | reg_to_bits(rm)) as u16;
2771                        let mut bytes = hw1.to_le_bytes().to_vec();
2772                        bytes.extend_from_slice(&hw2.to_le_bytes());
2773                        Ok(bytes)
2774                    }
2775                } else {
2776                    let instr: u16 = 0xBF00;
2777                    Ok(instr.to_le_bytes().to_vec())
2778                }
2779            }
2780
2781            // MOVW - Move Wide (Thumb-2 32-bit)
2782            ArmOp::Movw { rd, imm16 } => {
2783                self.encode_thumb32_movw_raw(reg_to_bits(rd), *imm16 as u32)
2784            }
2785
2786            // MOVT - Move Top (Thumb-2 32-bit)
2787            ArmOp::Movt { rd, imm16 } => {
2788                self.encode_thumb32_movt_raw(reg_to_bits(rd), *imm16 as u32)
2789            }
2790
2791            // #237: symbol-relative MOVW/MOVT. Encode the addend's low/high 16
2792            // bits in place; the backend records an R_ARM_MOVW_ABS_NC /
2793            // R_ARM_MOVT_ABS relocation against `symbol`, so the linker adds the
2794            // symbol's final address to the in-place addend (REL semantics).
2795            ArmOp::MovwSym { rd, addend, .. } => {
2796                self.encode_thumb32_movw_raw(reg_to_bits(rd), (*addend as u32) & 0xffff)
2797            }
2798            ArmOp::MovtSym { rd, addend, .. } => {
2799                self.encode_thumb32_movt_raw(reg_to_bits(rd), ((*addend as u32) >> 16) & 0xffff)
2800            }
2801
2802            // SetCond: Materialize condition flag into register (0 or 1)
2803            // Strategy: ITE <cond>; MOV Rd, #1; MOV Rd, #0
2804            // IMPORTANT: Must use ITE (If-Then-Else) because 16-bit Thumb MOV
2805            // always sets flags (MOVS). We need to evaluate the condition BEFORE
2806            // any MOV instruction clobbers the flags from CMP.
2807            ArmOp::SetCond { rd, cond } => {
2808                let rd_bits = reg_to_bits(rd) as u16;
2809
2810                // Condition code encoding for IT block
2811                use synth_synthesis::Condition;
2812                let cond_bits: u16 = match cond {
2813                    Condition::EQ => 0x0,
2814                    Condition::NE => 0x1,
2815                    Condition::LT => 0xB,
2816                    Condition::LE => 0xD,
2817                    Condition::GT => 0xC,
2818                    Condition::GE => 0xA,
2819                    Condition::LO => 0x3, // CC/LO (unsigned <)
2820                    Condition::LS => 0x9, // LS (unsigned <=)
2821                    Condition::HI => 0x8, // HI (unsigned >)
2822                    Condition::HS => 0x2, // CS/HS (unsigned >=)
2823                };
2824
2825                // ITE <cond>: encodes If-Then-Else block
2826                // The mask field depends on firstcond[0]:
2827                // - If firstcond[0] = 0: mask = 0xC for TE pattern (ITE EQ = BF0C)
2828                // - If firstcond[0] = 1: mask = 0x4 for TE pattern (ITE NE = BF14)
2829                let mask = if (cond_bits & 1) == 0 { 0xC } else { 0x4 };
2830                let ite_instr: u16 = 0xBF00 | (cond_bits << 4) | mask;
2831
2832                // Materialize 0/1 into Rd. The 16-bit MOVS (T1) encodes Rd in a
2833                // 3-bit field (bits[10:8]) — only R0–R7. For a high register
2834                // (R8–R12) `rd_bits << 8` overflows into bit 11 and silently
2835                // turns MOVS into CMP (00100 → 00101), corrupting the result
2836                // (this mis-materialized gale's `has_waiter`, so its `local.set`
2837                // stored a stale register → the binary-sem WAKE dispatch read
2838                // garbage). Use the 32-bit MOV.W (T2) for high registers, which
2839                // has a 4-bit Rd field. MOV.W with S=0 doesn't set flags, which
2840                // is fine inside the ITE (the materialized value is the result;
2841                // the flags are not consumed afterwards).
2842                let mut bytes = ite_instr.to_le_bytes().to_vec();
2843                let push_mov = |bytes: &mut Vec<u8>, imm: u16| {
2844                    if rd_bits <= 7 {
2845                        let m: u16 = 0x2000 | (rd_bits << 8) | imm; // 16-bit MOVS Rd,#imm
2846                        bytes.extend_from_slice(&m.to_le_bytes());
2847                    } else {
2848                        // 32-bit MOV.W Rd, #imm (T2): F04F | (Rd<<8) | imm8
2849                        let hw1: u16 = 0xF04F;
2850                        let hw2: u16 = (rd_bits << 8) | imm;
2851                        bytes.extend_from_slice(&hw1.to_le_bytes());
2852                        bytes.extend_from_slice(&hw2.to_le_bytes());
2853                    }
2854                };
2855                push_mov(&mut bytes, 1); // Then branch (condition true)  → 1
2856                push_mov(&mut bytes, 0); // Else branch (condition false) → 0
2857                Ok(bytes)
2858            }
2859
2860            // I64SetCond: Compare two i64 register pairs, result 0/1 in rd
2861            // EQ/NE: CMP lo,lo; IT EQ; CMPEQ hi,hi; ITE <cond>; MOV 1; MOV 0
2862            // LT: CMP lo,lo; SBCS rd,hi,hi; ITE LT; MOV 1; MOV 0
2863            // GT: CMP lo,lo (swapped); SBCS rd,hi,hi (swapped); ITE LT; MOV 1; MOV 0
2864            ArmOp::I64SetCond {
2865                rd,
2866                rn_lo,
2867                rn_hi,
2868                rm_lo,
2869                rm_hi,
2870                cond,
2871            } => {
2872                use synth_synthesis::Condition;
2873                let rd_bits = reg_to_bits(rd) as u16;
2874                let mut bytes = Vec::new();
2875
2876                // Helper: encode CMP Rn, Rm (16-bit)
2877                let encode_cmp_reg = |rn: &synth_synthesis::Reg,
2878                                      rm: &synth_synthesis::Reg|
2879                 -> Vec<u8> {
2880                    let rn_bits = reg_to_bits(rn) as u16;
2881                    let rm_bits = reg_to_bits(rm) as u16;
2882                    if rn_bits < 8 && rm_bits < 8 {
2883                        let instr: u16 = 0x4280 | (rm_bits << 3) | rn_bits;
2884                        instr.to_le_bytes().to_vec()
2885                    } else {
2886                        let n_bit = (rn_bits >> 3) & 1;
2887                        let instr: u16 = 0x4500 | (n_bit << 7) | (rm_bits << 3) | (rn_bits & 0x7);
2888                        instr.to_le_bytes().to_vec()
2889                    }
2890                };
2891
2892                // Helper: encode ITE <cond> (2 bytes)
2893                let encode_ite = |cond_bits: u16| -> Vec<u8> {
2894                    let mask = if (cond_bits & 1) == 0 { 0xC } else { 0x4 };
2895                    let ite_instr: u16 = 0xBF00 | (cond_bits << 4) | mask;
2896                    ite_instr.to_le_bytes().to_vec()
2897                };
2898
2899                // Helper: encode SetCond (ITE + MOV #1 + MOV #0) for given condition
2900                let encode_setcond = |cond_bits: u16, rd_bits: u16| -> Vec<u8> {
2901                    let mut b = encode_ite(cond_bits);
2902                    let mov_one: u16 = 0x2001 | (rd_bits << 8);
2903                    let mov_zero: u16 = 0x2000 | (rd_bits << 8);
2904                    b.extend_from_slice(&mov_one.to_le_bytes());
2905                    b.extend_from_slice(&mov_zero.to_le_bytes());
2906                    b
2907                };
2908
2909                match cond {
2910                    Condition::EQ | Condition::NE => {
2911                        // CMP rn_lo, rm_lo (compare low words)
2912                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
2913
2914                        // IT EQ (execute next instruction only if Z=1)
2915                        let it_eq: u16 = 0xBF08; // IT EQ: cond=0000, mask=1000
2916                        bytes.extend_from_slice(&it_eq.to_le_bytes());
2917
2918                        // CMPEQ rn_hi, rm_hi (compare high words, only if low equal)
2919                        bytes.extend_from_slice(&encode_cmp_reg(rn_hi, rm_hi));
2920
2921                        // ITE <cond>; MOV rd, #1; MOV rd, #0
2922                        let cond_bits: u16 = match cond {
2923                            Condition::EQ => 0x0,
2924                            Condition::NE => 0x1,
2925                            _ => unreachable!(),
2926                        };
2927                        bytes.extend_from_slice(&encode_setcond(cond_bits, rd_bits));
2928                    }
2929
2930                    Condition::LT => {
2931                        // CMP rn_lo, rm_lo (sets C flag for borrow)
2932                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
2933
2934                        // SBCS rd, rn_hi, rm_hi (subtract with carry, sets N,V flags)
2935                        // SBCS.W Rd, Rn, Rm: EB70 Rn | 0000 Rd 0000 Rm
2936                        let rn_hi_bits = reg_to_bits(rn_hi);
2937                        let rm_hi_bits = reg_to_bits(rm_hi);
2938                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
2939                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
2940                        bytes.extend_from_slice(&hw1.to_le_bytes());
2941                        bytes.extend_from_slice(&hw2.to_le_bytes());
2942
2943                        // ITE LT; MOV rd, #1; MOV rd, #0
2944                        bytes.extend_from_slice(&encode_setcond(0xB, rd_bits)); // LT = 0xB
2945                    }
2946
2947                    Condition::GT => {
2948                        // GT(a,b) = LT(b,a): swap operands
2949                        // CMP rm_lo, rn_lo (swapped)
2950                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
2951
2952                        // SBCS rd, rm_hi, rn_hi (swapped)
2953                        let rm_hi_bits = reg_to_bits(rm_hi);
2954                        let rn_hi_bits = reg_to_bits(rn_hi);
2955                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
2956                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
2957                        bytes.extend_from_slice(&hw1.to_le_bytes());
2958                        bytes.extend_from_slice(&hw2.to_le_bytes());
2959
2960                        // ITE LT; MOV rd, #1; MOV rd, #0
2961                        bytes.extend_from_slice(&encode_setcond(0xB, rd_bits)); // LT = 0xB
2962                    }
2963
2964                    Condition::LE => {
2965                        // LE(a,b) = !GT(a,b): use GT logic but invert result
2966                        // GT(a,b) = LT(b,a): so we do CMP(b,a) and check LT, then invert
2967                        // CMP rm_lo, rn_lo (swapped, same as GT)
2968                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
2969
2970                        // SBCS rd, rm_hi, rn_hi (swapped)
2971                        let rm_hi_bits = reg_to_bits(rm_hi);
2972                        let rn_hi_bits = reg_to_bits(rn_hi);
2973                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
2974                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
2975                        bytes.extend_from_slice(&hw1.to_le_bytes());
2976                        bytes.extend_from_slice(&hw2.to_le_bytes());
2977
2978                        // ITE GE; MOV rd, #1; MOV rd, #0 (GE is !LT, so inverting GT result)
2979                        bytes.extend_from_slice(&encode_setcond(0xA, rd_bits)); // GE = 0xA
2980                    }
2981
2982                    Condition::GE => {
2983                        // GE(a,b) = !LT(a,b): use LT logic but invert result
2984                        // CMP rn_lo, rm_lo (same as LT)
2985                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
2986
2987                        // SBCS rd, rn_hi, rm_hi (same as LT)
2988                        let rn_hi_bits = reg_to_bits(rn_hi);
2989                        let rm_hi_bits = reg_to_bits(rm_hi);
2990                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
2991                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
2992                        bytes.extend_from_slice(&hw1.to_le_bytes());
2993                        bytes.extend_from_slice(&hw2.to_le_bytes());
2994
2995                        // ITE GE; MOV rd, #1; MOV rd, #0 (GE is !LT)
2996                        bytes.extend_from_slice(&encode_setcond(0xA, rd_bits)); // GE = 0xA
2997                    }
2998
2999                    // Unsigned comparisons - same instruction sequence, different conditions
3000                    Condition::LO => {
3001                        // LO (unsigned LT): CMP lo, SBCS hi, check C=0
3002                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
3003                        let rn_hi_bits = reg_to_bits(rn_hi);
3004                        let rm_hi_bits = reg_to_bits(rm_hi);
3005                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
3006                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
3007                        bytes.extend_from_slice(&hw1.to_le_bytes());
3008                        bytes.extend_from_slice(&hw2.to_le_bytes());
3009                        bytes.extend_from_slice(&encode_setcond(0x3, rd_bits)); // LO = 0x3 (CC)
3010                    }
3011
3012                    Condition::HI => {
3013                        // HI (unsigned GT): swap operands and check LO
3014                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
3015                        let rm_hi_bits = reg_to_bits(rm_hi);
3016                        let rn_hi_bits = reg_to_bits(rn_hi);
3017                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
3018                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
3019                        bytes.extend_from_slice(&hw1.to_le_bytes());
3020                        bytes.extend_from_slice(&hw2.to_le_bytes());
3021                        bytes.extend_from_slice(&encode_setcond(0x3, rd_bits)); // LO = 0x3 (CC)
3022                    }
3023
3024                    Condition::LS => {
3025                        // LS (unsigned LE): !(a > b) = !(HI), so do HI and invert
3026                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
3027                        let rm_hi_bits = reg_to_bits(rm_hi);
3028                        let rn_hi_bits = reg_to_bits(rn_hi);
3029                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
3030                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
3031                        bytes.extend_from_slice(&hw1.to_le_bytes());
3032                        bytes.extend_from_slice(&hw2.to_le_bytes());
3033                        bytes.extend_from_slice(&encode_setcond(0x2, rd_bits)); // HS = 0x2 (CS) = !LO
3034                    }
3035
3036                    Condition::HS => {
3037                        // HS (unsigned GE): !(a < b) = !(LO)
3038                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
3039                        let rn_hi_bits = reg_to_bits(rn_hi);
3040                        let rm_hi_bits = reg_to_bits(rm_hi);
3041                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
3042                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
3043                        bytes.extend_from_slice(&hw1.to_le_bytes());
3044                        bytes.extend_from_slice(&hw2.to_le_bytes());
3045                        bytes.extend_from_slice(&encode_setcond(0x2, rd_bits)); // HS = 0x2 (CS) = !LO
3046                    }
3047                }
3048
3049                Ok(bytes)
3050            }
3051
3052            // I64SetCondZ: Test if i64 register pair is zero, result 0/1 in rd
3053            // ORR.W rd, rn_lo, rn_hi; CMP rd, #0; ITE EQ; MOV 1; MOV 0
3054            ArmOp::I64SetCondZ { rd, rn_lo, rn_hi } => {
3055                let rd_bits = reg_to_bits(rd);
3056                let rn_lo_bits = reg_to_bits(rn_lo);
3057                let rn_hi_bits = reg_to_bits(rn_hi);
3058                let mut bytes = Vec::new();
3059
3060                // ORR.W rd, rn_lo, rn_hi: EA40 rn_lo | 0000 rd 0000 rn_hi
3061                let hw1: u16 = (0xEA40 | rn_lo_bits) as u16;
3062                let hw2: u16 = ((rd_bits << 8) | rn_hi_bits) as u16;
3063                bytes.extend_from_slice(&hw1.to_le_bytes());
3064                bytes.extend_from_slice(&hw2.to_le_bytes());
3065
3066                // CMP rd, #0 (16-bit): 0010 1 Rd 0000 0000
3067                let cmp_instr: u16 = 0x2800 | ((rd_bits as u16) << 8);
3068                bytes.extend_from_slice(&cmp_instr.to_le_bytes());
3069
3070                // ITE EQ; MOV rd, #1; MOV rd, #0
3071                let mask = 0xC_u16; // ITE EQ mask: firstcond[0]=0, mask=0xC
3072                let ite_instr: u16 = 0xBF00 | mask;
3073                bytes.extend_from_slice(&ite_instr.to_le_bytes());
3074                let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
3075                let mov_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
3076                bytes.extend_from_slice(&mov_one.to_le_bytes());
3077                bytes.extend_from_slice(&mov_zero.to_le_bytes());
3078
3079                Ok(bytes)
3080            }
3081
3082            // I64Mul: 64-bit multiply using UMULL + MLA cross products
3083            // Formula: result = (a_lo * b_lo) + ((a_lo * b_hi + a_hi * b_lo) << 32)
3084            // Uses R12 as scratch register
3085            ArmOp::I64Mul {
3086                rd_lo,
3087                rd_hi,
3088                rn_lo,
3089                rn_hi,
3090                rm_lo,
3091                rm_hi,
3092            } => {
3093                let rd_lo_bits = reg_to_bits(rd_lo);
3094                let rd_hi_bits = reg_to_bits(rd_hi);
3095                let rn_lo_bits = reg_to_bits(rn_lo);
3096                let rn_hi_bits = reg_to_bits(rn_hi);
3097                let rm_lo_bits = reg_to_bits(rm_lo);
3098                let rm_hi_bits = reg_to_bits(rm_hi);
3099                let r12: u32 = 12; // IP scratch register
3100                let mut bytes = Vec::new();
3101
3102                // 1. MUL R12, rn_lo, rm_hi  (R12 = a_lo * b_hi)
3103                // Thumb-2 MUL: hw1=0xFB00|Rn, hw2=0xF000|(Rd<<8)|Rm
3104                let hw1: u16 = (0xFB00 | rn_lo_bits) as u16;
3105                let hw2: u16 = (0xF000 | (r12 << 8) | rm_hi_bits) as u16;
3106                bytes.extend_from_slice(&hw1.to_le_bytes());
3107                bytes.extend_from_slice(&hw2.to_le_bytes());
3108
3109                // 2. MLA R12, rn_hi, rm_lo, R12  (R12 += a_hi * b_lo)
3110                // Thumb-2 MLA: hw1=0xFB00|Rn, hw2=(Ra<<12)|(Rd<<8)|Rm
3111                let hw1: u16 = (0xFB00 | rn_hi_bits) as u16;
3112                let hw2: u16 = ((r12 << 12) | (r12 << 8) | rm_lo_bits) as u16;
3113                bytes.extend_from_slice(&hw1.to_le_bytes());
3114                bytes.extend_from_slice(&hw2.to_le_bytes());
3115
3116                // 3. UMULL rd_lo, rd_hi, rn_lo, rm_lo  (rd_lo:rd_hi = a_lo * b_lo)
3117                // Thumb-2 UMULL: hw1=0xFBA0|Rn, hw2=(RdLo<<12)|(RdHi<<8)|Rm
3118                let hw1: u16 = (0xFBA0 | rn_lo_bits) as u16;
3119                let hw2: u16 = ((rd_lo_bits << 12) | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3120                bytes.extend_from_slice(&hw1.to_le_bytes());
3121                bytes.extend_from_slice(&hw2.to_le_bytes());
3122
3123                // 4. ADD rd_hi, R12  (rd_hi += cross products)
3124                // 16-bit high reg ADD: 01000100 D Rm Rdn[2:0]
3125                let d_bit = (rd_hi_bits >> 3) & 1;
3126                let add_instr: u16 =
3127                    (0x4400 | (d_bit << 7) | (r12 << 3) | (rd_hi_bits & 0x7)) as u16;
3128                bytes.extend_from_slice(&add_instr.to_le_bytes());
3129
3130                Ok(bytes)
3131            }
3132
3133            // I64Shl: 64-bit shift left with branch for n<32 vs n>=32
3134            // rm_hi (R3) is used as temp register
3135            ArmOp::I64Shl {
3136                rd_lo,
3137                rd_hi,
3138                rn_lo,
3139                rn_hi,
3140                rm_lo,
3141                rm_hi,
3142            } => {
3143                let rd_lo_bits = reg_to_bits(rd_lo);
3144                let rd_hi_bits = reg_to_bits(rd_hi);
3145                let rn_lo_bits = reg_to_bits(rn_lo);
3146                let rn_hi_bits = reg_to_bits(rn_hi);
3147                let rm_lo_bits = reg_to_bits(rm_lo);
3148                let rm_hi_bits = reg_to_bits(rm_hi); // temp
3149                let mut bytes = Vec::new();
3150
3151                // AND.W rm_lo, rm_lo, #63  (mask shift amount to 6 bits)
3152                let hw1: u16 = (0xF000 | rm_lo_bits) as u16;
3153                let hw2: u16 = ((rm_lo_bits << 8) | 0x3F) as u16;
3154                bytes.extend_from_slice(&hw1.to_le_bytes());
3155                bytes.extend_from_slice(&hw2.to_le_bytes());
3156
3157                // SUBS.W rm_hi, rm_lo, #32  (rm_hi = n-32, sets flags)
3158                let hw1: u16 = (0xF1B0 | rm_lo_bits) as u16;
3159                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3160                bytes.extend_from_slice(&hw1.to_le_bytes());
3161                bytes.extend_from_slice(&hw2.to_le_bytes());
3162
3163                // BPL .large (branch if n >= 32, offset = +10 halfwords)
3164                let bpl: u16 = 0xD50A;
3165                bytes.extend_from_slice(&bpl.to_le_bytes());
3166
3167                // --- Small shift (n < 32) ---
3168                // RSB.W rm_hi, rm_lo, #32  (rm_hi = 32-n)
3169                let hw1: u16 = (0xF1C0 | rm_lo_bits) as u16;
3170                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3171                bytes.extend_from_slice(&hw1.to_le_bytes());
3172                bytes.extend_from_slice(&hw2.to_le_bytes());
3173
3174                // LSR.W rm_hi, rn_lo, rm_hi  (rm_hi = lo >> (32-n), overflow bits)
3175                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3176                let hw2: u16 = (0xF000 | (rm_hi_bits << 8) | rm_hi_bits) as u16;
3177                bytes.extend_from_slice(&hw1.to_le_bytes());
3178                bytes.extend_from_slice(&hw2.to_le_bytes());
3179
3180                // LSL.W rd_hi, rn_hi, rm_lo  (hi <<= n)
3181                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3182                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3183                bytes.extend_from_slice(&hw1.to_le_bytes());
3184                bytes.extend_from_slice(&hw2.to_le_bytes());
3185
3186                // ORR.W rd_hi, rd_hi, rm_hi  (hi |= overflow bits from lo)
3187                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3188                let hw2: u16 = ((rd_hi_bits << 8) | rm_hi_bits) as u16;
3189                bytes.extend_from_slice(&hw1.to_le_bytes());
3190                bytes.extend_from_slice(&hw2.to_le_bytes());
3191
3192                // LSL.W rd_lo, rn_lo, rm_lo  (lo <<= n)
3193                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3194                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_lo_bits) as u16;
3195                bytes.extend_from_slice(&hw1.to_le_bytes());
3196                bytes.extend_from_slice(&hw2.to_le_bytes());
3197
3198                // B .done (skip large shift: +2 halfwords)
3199                let b_done: u16 = 0xE002;
3200                bytes.extend_from_slice(&b_done.to_le_bytes());
3201
3202                // --- Large shift (n >= 32) ---
3203                // LSL.W rd_hi, rn_lo, rm_hi  (hi = lo << (n-32))
3204                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3205                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_hi_bits) as u16;
3206                bytes.extend_from_slice(&hw1.to_le_bytes());
3207                bytes.extend_from_slice(&hw2.to_le_bytes());
3208
3209                // MOV rd_lo, #0
3210                let mov_zero: u16 = 0x2000 | ((rd_lo_bits as u16) << 8);
3211                bytes.extend_from_slice(&mov_zero.to_le_bytes());
3212
3213                Ok(bytes) // Total: 38 bytes
3214            }
3215
3216            // I64ShrU: 64-bit logical shift right with branch for n<32 vs n>=32
3217            ArmOp::I64ShrU {
3218                rd_lo,
3219                rd_hi,
3220                rn_lo,
3221                rn_hi,
3222                rm_lo,
3223                rm_hi,
3224            } => {
3225                let rd_lo_bits = reg_to_bits(rd_lo);
3226                let rd_hi_bits = reg_to_bits(rd_hi);
3227                let rn_lo_bits = reg_to_bits(rn_lo);
3228                let rn_hi_bits = reg_to_bits(rn_hi);
3229                let rm_lo_bits = reg_to_bits(rm_lo);
3230                let rm_hi_bits = reg_to_bits(rm_hi); // temp
3231                let mut bytes = Vec::new();
3232
3233                // AND.W rm_lo, rm_lo, #63
3234                let hw1: u16 = (0xF000 | rm_lo_bits) as u16;
3235                let hw2: u16 = ((rm_lo_bits << 8) | 0x3F) as u16;
3236                bytes.extend_from_slice(&hw1.to_le_bytes());
3237                bytes.extend_from_slice(&hw2.to_le_bytes());
3238
3239                // SUBS.W rm_hi, rm_lo, #32
3240                let hw1: u16 = (0xF1B0 | rm_lo_bits) as u16;
3241                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3242                bytes.extend_from_slice(&hw1.to_le_bytes());
3243                bytes.extend_from_slice(&hw2.to_le_bytes());
3244
3245                // BPL .large (+10 halfwords)
3246                let bpl: u16 = 0xD50A;
3247                bytes.extend_from_slice(&bpl.to_le_bytes());
3248
3249                // --- Small shift (n < 32) ---
3250                // RSB.W rm_hi, rm_lo, #32  (rm_hi = 32-n)
3251                let hw1: u16 = (0xF1C0 | rm_lo_bits) as u16;
3252                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3253                bytes.extend_from_slice(&hw1.to_le_bytes());
3254                bytes.extend_from_slice(&hw2.to_le_bytes());
3255
3256                // LSL.W rm_hi, rn_hi, rm_hi  (rm_hi = hi << (32-n), bits flowing to lo)
3257                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3258                let hw2: u16 = (0xF000 | (rm_hi_bits << 8) | rm_hi_bits) as u16;
3259                bytes.extend_from_slice(&hw1.to_le_bytes());
3260                bytes.extend_from_slice(&hw2.to_le_bytes());
3261
3262                // LSR.W rd_lo, rn_lo, rm_lo  (lo >>= n)
3263                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3264                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_lo_bits) as u16;
3265                bytes.extend_from_slice(&hw1.to_le_bytes());
3266                bytes.extend_from_slice(&hw2.to_le_bytes());
3267
3268                // ORR.W rd_lo, rd_lo, rm_hi  (lo |= overflow from hi)
3269                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3270                let hw2: u16 = ((rd_lo_bits << 8) | rm_hi_bits) as u16;
3271                bytes.extend_from_slice(&hw1.to_le_bytes());
3272                bytes.extend_from_slice(&hw2.to_le_bytes());
3273
3274                // LSR.W rd_hi, rn_hi, rm_lo  (hi >>= n, logical)
3275                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3276                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3277                bytes.extend_from_slice(&hw1.to_le_bytes());
3278                bytes.extend_from_slice(&hw2.to_le_bytes());
3279
3280                // B .done (+2 halfwords)
3281                let b_done: u16 = 0xE002;
3282                bytes.extend_from_slice(&b_done.to_le_bytes());
3283
3284                // --- Large shift (n >= 32) ---
3285                // LSR.W rd_lo, rn_hi, rm_hi  (lo = hi >> (n-32))
3286                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3287                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_hi_bits) as u16;
3288                bytes.extend_from_slice(&hw1.to_le_bytes());
3289                bytes.extend_from_slice(&hw2.to_le_bytes());
3290
3291                // MOV rd_hi, #0
3292                let mov_zero: u16 = 0x2000 | ((rd_hi_bits as u16) << 8);
3293                bytes.extend_from_slice(&mov_zero.to_le_bytes());
3294
3295                Ok(bytes) // Total: 38 bytes
3296            }
3297
3298            // I64ShrS: 64-bit arithmetic shift right with branch for n<32 vs n>=32
3299            ArmOp::I64ShrS {
3300                rd_lo,
3301                rd_hi,
3302                rn_lo,
3303                rn_hi,
3304                rm_lo,
3305                rm_hi,
3306            } => {
3307                let rd_lo_bits = reg_to_bits(rd_lo);
3308                let rd_hi_bits = reg_to_bits(rd_hi);
3309                let rn_lo_bits = reg_to_bits(rn_lo);
3310                let rn_hi_bits = reg_to_bits(rn_hi);
3311                let rm_lo_bits = reg_to_bits(rm_lo);
3312                let rm_hi_bits = reg_to_bits(rm_hi); // temp
3313                let mut bytes = Vec::new();
3314
3315                // AND.W rm_lo, rm_lo, #63
3316                let hw1: u16 = (0xF000 | rm_lo_bits) as u16;
3317                let hw2: u16 = ((rm_lo_bits << 8) | 0x3F) as u16;
3318                bytes.extend_from_slice(&hw1.to_le_bytes());
3319                bytes.extend_from_slice(&hw2.to_le_bytes());
3320
3321                // SUBS.W rm_hi, rm_lo, #32
3322                let hw1: u16 = (0xF1B0 | rm_lo_bits) as u16;
3323                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3324                bytes.extend_from_slice(&hw1.to_le_bytes());
3325                bytes.extend_from_slice(&hw2.to_le_bytes());
3326
3327                // BPL .large (+10 halfwords)
3328                let bpl: u16 = 0xD50A;
3329                bytes.extend_from_slice(&bpl.to_le_bytes());
3330
3331                // --- Small shift (n < 32) ---
3332                // RSB.W rm_hi, rm_lo, #32
3333                let hw1: u16 = (0xF1C0 | rm_lo_bits) as u16;
3334                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3335                bytes.extend_from_slice(&hw1.to_le_bytes());
3336                bytes.extend_from_slice(&hw2.to_le_bytes());
3337
3338                // LSL.W rm_hi, rn_hi, rm_hi  (rm_hi = hi << (32-n), bits flowing to lo)
3339                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3340                let hw2: u16 = (0xF000 | (rm_hi_bits << 8) | rm_hi_bits) as u16;
3341                bytes.extend_from_slice(&hw1.to_le_bytes());
3342                bytes.extend_from_slice(&hw2.to_le_bytes());
3343
3344                // LSR.W rd_lo, rn_lo, rm_lo  (lo >>= n, logical for lo word)
3345                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3346                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_lo_bits) as u16;
3347                bytes.extend_from_slice(&hw1.to_le_bytes());
3348                bytes.extend_from_slice(&hw2.to_le_bytes());
3349
3350                // ORR.W rd_lo, rd_lo, rm_hi  (lo |= overflow from hi)
3351                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3352                let hw2: u16 = ((rd_lo_bits << 8) | rm_hi_bits) as u16;
3353                bytes.extend_from_slice(&hw1.to_le_bytes());
3354                bytes.extend_from_slice(&hw2.to_le_bytes());
3355
3356                // ASR.W rd_hi, rn_hi, rm_lo  (hi >>= n, arithmetic/sign-extending)
3357                let hw1: u16 = (0xFA40 | rn_hi_bits) as u16;
3358                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3359                bytes.extend_from_slice(&hw1.to_le_bytes());
3360                bytes.extend_from_slice(&hw2.to_le_bytes());
3361
3362                // B .done (+3 halfwords, large shift is 8 bytes)
3363                let b_done: u16 = 0xE003;
3364                bytes.extend_from_slice(&b_done.to_le_bytes());
3365
3366                // --- Large shift (n >= 32) ---
3367                // ASR.W rd_lo, rn_hi, rm_hi  (lo = hi >>> (n-32))
3368                let hw1: u16 = (0xFA40 | rn_hi_bits) as u16;
3369                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_hi_bits) as u16;
3370                bytes.extend_from_slice(&hw1.to_le_bytes());
3371                bytes.extend_from_slice(&hw2.to_le_bytes());
3372
3373                // ASR.W rd_hi, rn_hi, #31  (hi = sign extension, all 0s or all 1s)
3374                // Thumb-2 ASR immediate: hw1=0xEA4F, hw2=imm3:Rd:imm2:10:Rm
3375                // imm5=31=11111 → imm3=111, imm2=11
3376                let hw1: u16 = 0xEA4F;
3377                let hw2: u16 = (0x7000 | (rd_hi_bits << 8) | 0x00E0 | rn_hi_bits) as u16;
3378                bytes.extend_from_slice(&hw1.to_le_bytes());
3379                bytes.extend_from_slice(&hw2.to_le_bytes());
3380
3381                Ok(bytes) // Total: 40 bytes
3382            }
3383
3384            // I64Rotl: 64-bit rotate left
3385            // For n < 32: new_hi = (hi << n) | (lo >> (32-n)), new_lo = (lo << n) | (hi >> (32-n))
3386            // For n >= 32: same formula but with lo/hi conceptually swapped, shift by (n-32)
3387            // Uses R4 (saved/restored) and R12 as scratch
3388            ArmOp::I64Rotl {
3389                rdlo,
3390                rdhi,
3391                rnlo,
3392                rnhi,
3393                shift,
3394            } => {
3395                let rd_lo_bits = reg_to_bits(rdlo);
3396                let rd_hi_bits = reg_to_bits(rdhi);
3397                let rn_lo_bits = reg_to_bits(rnlo);
3398                let rn_hi_bits = reg_to_bits(rnhi);
3399                let shift_bits = reg_to_bits(shift);
3400                let r12: u32 = 12; // IP scratch
3401                let r3: u32 = 3; // Scratch (high word of shift amount, unused)
3402                let r4: u32 = 4; // Scratch (saved/restored)
3403                let mut bytes = Vec::new();
3404
3405                // PUSH {R4}
3406                bytes.extend_from_slice(&0xB410u16.to_le_bytes());
3407
3408                // AND.W shift, shift, #63 (mask to 6 bits)
3409                let hw1: u16 = (0xF000 | shift_bits) as u16;
3410                let hw2: u16 = ((shift_bits << 8) | 0x3F) as u16;
3411                bytes.extend_from_slice(&hw1.to_le_bytes());
3412                bytes.extend_from_slice(&hw2.to_le_bytes());
3413
3414                // SUBS.W R3, shift, #32 (R3 = n-32, sets flags)
3415                let hw1: u16 = (0xF1B0 | shift_bits) as u16;
3416                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3417                bytes.extend_from_slice(&hw1.to_le_bytes());
3418                bytes.extend_from_slice(&hw2.to_le_bytes());
3419
3420                // BPL .large (branch if n >= 32, offset = +14 halfwords)
3421                let bpl: u16 = 0xD50E;
3422                bytes.extend_from_slice(&bpl.to_le_bytes());
3423
3424                // === Small rotation (n < 32) ===
3425                // RSB.W R3, shift, #32 (R3 = 32-n)
3426                let hw1: u16 = (0xF1C0 | shift_bits) as u16;
3427                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3428                bytes.extend_from_slice(&hw1.to_le_bytes());
3429                bytes.extend_from_slice(&hw2.to_le_bytes());
3430
3431                // LSR.W R4, rn_lo, R3 (R4 = lo >> (32-n), will go to new_hi)
3432                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3433                let hw2: u16 = (0xF000 | (r4 << 8) | r3) as u16;
3434                bytes.extend_from_slice(&hw1.to_le_bytes());
3435                bytes.extend_from_slice(&hw2.to_le_bytes());
3436
3437                // LSR.W R12, rn_hi, R3 (R12 = hi >> (32-n), will go to new_lo)
3438                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3439                let hw2: u16 = (0xF000 | (r12 << 8) | r3) as u16;
3440                bytes.extend_from_slice(&hw1.to_le_bytes());
3441                bytes.extend_from_slice(&hw2.to_le_bytes());
3442
3443                // LSL.W rd_hi, rn_hi, shift (rd_hi = hi << n)
3444                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3445                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | shift_bits) as u16;
3446                bytes.extend_from_slice(&hw1.to_le_bytes());
3447                bytes.extend_from_slice(&hw2.to_le_bytes());
3448
3449                // ORR.W rd_hi, rd_hi, R4 (rd_hi = (hi << n) | (lo >> (32-n)))
3450                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3451                let hw2: u16 = ((rd_hi_bits << 8) | r4) as u16;
3452                bytes.extend_from_slice(&hw1.to_le_bytes());
3453                bytes.extend_from_slice(&hw2.to_le_bytes());
3454
3455                // LSL.W rd_lo, rn_lo, shift (rd_lo = lo << n)
3456                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3457                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | shift_bits) as u16;
3458                bytes.extend_from_slice(&hw1.to_le_bytes());
3459                bytes.extend_from_slice(&hw2.to_le_bytes());
3460
3461                // ORR.W rd_lo, rd_lo, R12 (rd_lo = (lo << n) | (hi >> (32-n)))
3462                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3463                let hw2: u16 = ((rd_lo_bits << 8) | r12) as u16;
3464                bytes.extend_from_slice(&hw1.to_le_bytes());
3465                bytes.extend_from_slice(&hw2.to_le_bytes());
3466
3467                // B .done (skip large block, offset = +14 halfwords)
3468                let b_done: u16 = 0xE00E;
3469                bytes.extend_from_slice(&b_done.to_le_bytes());
3470
3471                // === Large rotation (n >= 32) ===
3472                // R3 already has n-32 from the SUBS
3473                // RSB.W R4, R3, #32 (R4 = 32-(n-32) = 64-n)
3474                let hw1: u16 = (0xF1C0 | r3) as u16;
3475                let hw2: u16 = ((r4 << 8) | 0x20) as u16;
3476                bytes.extend_from_slice(&hw1.to_le_bytes());
3477                bytes.extend_from_slice(&hw2.to_le_bytes());
3478
3479                // LSR.W R12, rn_hi, R4 (R12 = hi >> (64-n), goes to new_hi low bits)
3480                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3481                let hw2: u16 = (0xF000 | (r12 << 8) | r4) as u16;
3482                bytes.extend_from_slice(&hw1.to_le_bytes());
3483                bytes.extend_from_slice(&hw2.to_le_bytes());
3484
3485                // LSR.W R4, rn_lo, R4 (R4 = lo >> (64-n), goes to new_lo low bits)
3486                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3487                let hw2: u16 = (0xF000 | (r4 << 8) | r4) as u16;
3488                bytes.extend_from_slice(&hw1.to_le_bytes());
3489                bytes.extend_from_slice(&hw2.to_le_bytes());
3490
3491                // LSL.W shift, rn_lo, R3 (shift = lo << (n-32), new_hi high bits)
3492                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3493                let hw2: u16 = (0xF000 | (shift_bits << 8) | r3) as u16;
3494                bytes.extend_from_slice(&hw1.to_le_bytes());
3495                bytes.extend_from_slice(&hw2.to_le_bytes());
3496
3497                // ORR.W shift, shift, R12 (shift = (lo << (n-32)) | (hi >> (64-n)) = new_hi)
3498                let hw1: u16 = (0xEA40 | shift_bits) as u16;
3499                let hw2: u16 = ((shift_bits << 8) | r12) as u16;
3500                bytes.extend_from_slice(&hw1.to_le_bytes());
3501                bytes.extend_from_slice(&hw2.to_le_bytes());
3502
3503                // LSL.W rd_lo, rn_hi, R3 (rd_lo = hi << (n-32), new_lo high bits)
3504                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3505                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | r3) as u16;
3506                bytes.extend_from_slice(&hw1.to_le_bytes());
3507                bytes.extend_from_slice(&hw2.to_le_bytes());
3508
3509                // ORR.W rd_lo, rd_lo, R4 (rd_lo = (hi << (n-32)) | (lo >> (64-n)) = new_lo)
3510                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3511                let hw2: u16 = ((rd_lo_bits << 8) | r4) as u16;
3512                bytes.extend_from_slice(&hw1.to_le_bytes());
3513                bytes.extend_from_slice(&hw2.to_le_bytes());
3514
3515                // MOV rd_hi, shift (rd_hi = new_hi)
3516                let d_bit = (rd_hi_bits >> 3) & 1;
3517                let mov_instr: u16 =
3518                    (0x4600 | (d_bit << 7) | (shift_bits << 3) | (rd_hi_bits & 0x7)) as u16;
3519                bytes.extend_from_slice(&mov_instr.to_le_bytes());
3520
3521                // POP {R4}
3522                bytes.extend_from_slice(&0xBC10u16.to_le_bytes());
3523
3524                Ok(bytes) // Total: 74 bytes
3525            }
3526
3527            // I64Rotr: 64-bit rotate right
3528            // rotr(x, n) = rotl(x, 64-n)
3529            // For n < 32: new_lo = (lo >> n) | (hi << (32-n)), new_hi = (hi >> n) | (lo << (32-n))
3530            // For n >= 32: same formula but with lo/hi swapped, shift by (n-32)
3531            ArmOp::I64Rotr {
3532                rdlo,
3533                rdhi,
3534                rnlo,
3535                rnhi,
3536                shift,
3537            } => {
3538                let rd_lo_bits = reg_to_bits(rdlo);
3539                let rd_hi_bits = reg_to_bits(rdhi);
3540                let rn_lo_bits = reg_to_bits(rnlo);
3541                let rn_hi_bits = reg_to_bits(rnhi);
3542                let shift_bits = reg_to_bits(shift);
3543                let r12: u32 = 12;
3544                let r3: u32 = 3;
3545                let r4: u32 = 4;
3546                let mut bytes = Vec::new();
3547
3548                // PUSH {R4}
3549                bytes.extend_from_slice(&0xB410u16.to_le_bytes());
3550
3551                // AND.W shift, shift, #63
3552                let hw1: u16 = (0xF000 | shift_bits) as u16;
3553                let hw2: u16 = ((shift_bits << 8) | 0x3F) as u16;
3554                bytes.extend_from_slice(&hw1.to_le_bytes());
3555                bytes.extend_from_slice(&hw2.to_le_bytes());
3556
3557                // SUBS.W R3, shift, #32
3558                let hw1: u16 = (0xF1B0 | shift_bits) as u16;
3559                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3560                bytes.extend_from_slice(&hw1.to_le_bytes());
3561                bytes.extend_from_slice(&hw2.to_le_bytes());
3562
3563                // BPL .large (+14 halfwords)
3564                let bpl: u16 = 0xD50E;
3565                bytes.extend_from_slice(&bpl.to_le_bytes());
3566
3567                // === Small rotation (n < 32) ===
3568                // RSB.W R3, shift, #32 (R3 = 32-n)
3569                let hw1: u16 = (0xF1C0 | shift_bits) as u16;
3570                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3571                bytes.extend_from_slice(&hw1.to_le_bytes());
3572                bytes.extend_from_slice(&hw2.to_le_bytes());
3573
3574                // LSL.W R4, rn_hi, R3 (R4 = hi << (32-n), will go to new_lo)
3575                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3576                let hw2: u16 = (0xF000 | (r4 << 8) | r3) as u16;
3577                bytes.extend_from_slice(&hw1.to_le_bytes());
3578                bytes.extend_from_slice(&hw2.to_le_bytes());
3579
3580                // LSL.W R12, rn_lo, R3 (R12 = lo << (32-n), will go to new_hi)
3581                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3582                let hw2: u16 = (0xF000 | (r12 << 8) | r3) as u16;
3583                bytes.extend_from_slice(&hw1.to_le_bytes());
3584                bytes.extend_from_slice(&hw2.to_le_bytes());
3585
3586                // LSR.W rd_lo, rn_lo, shift (rd_lo = lo >> n)
3587                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3588                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | shift_bits) as u16;
3589                bytes.extend_from_slice(&hw1.to_le_bytes());
3590                bytes.extend_from_slice(&hw2.to_le_bytes());
3591
3592                // ORR.W rd_lo, rd_lo, R4 (rd_lo = (lo >> n) | (hi << (32-n)))
3593                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3594                let hw2: u16 = ((rd_lo_bits << 8) | r4) as u16;
3595                bytes.extend_from_slice(&hw1.to_le_bytes());
3596                bytes.extend_from_slice(&hw2.to_le_bytes());
3597
3598                // LSR.W rd_hi, rn_hi, shift (rd_hi = hi >> n)
3599                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3600                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | shift_bits) as u16;
3601                bytes.extend_from_slice(&hw1.to_le_bytes());
3602                bytes.extend_from_slice(&hw2.to_le_bytes());
3603
3604                // ORR.W rd_hi, rd_hi, R12 (rd_hi = (hi >> n) | (lo << (32-n)))
3605                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3606                let hw2: u16 = ((rd_hi_bits << 8) | r12) as u16;
3607                bytes.extend_from_slice(&hw1.to_le_bytes());
3608                bytes.extend_from_slice(&hw2.to_le_bytes());
3609
3610                // B .done (+14 halfwords)
3611                let b_done: u16 = 0xE00E;
3612                bytes.extend_from_slice(&b_done.to_le_bytes());
3613
3614                // === Large rotation (n >= 32) ===
3615                // RSB.W R4, R3, #32 (R4 = 64-n)
3616                let hw1: u16 = (0xF1C0 | r3) as u16;
3617                let hw2: u16 = ((r4 << 8) | 0x20) as u16;
3618                bytes.extend_from_slice(&hw1.to_le_bytes());
3619                bytes.extend_from_slice(&hw2.to_le_bytes());
3620
3621                // LSL.W R12, rn_lo, R4 (R12 = lo << (64-n), goes to new_lo low bits)
3622                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3623                let hw2: u16 = (0xF000 | (r12 << 8) | r4) as u16;
3624                bytes.extend_from_slice(&hw1.to_le_bytes());
3625                bytes.extend_from_slice(&hw2.to_le_bytes());
3626
3627                // LSL.W R4, rn_hi, R4 (R4 = hi << (64-n), goes to new_hi low bits)
3628                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3629                let hw2: u16 = (0xF000 | (r4 << 8) | r4) as u16;
3630                bytes.extend_from_slice(&hw1.to_le_bytes());
3631                bytes.extend_from_slice(&hw2.to_le_bytes());
3632
3633                // LSR.W shift, rn_hi, R3 (shift = hi >> (n-32), new_lo high bits)
3634                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3635                let hw2: u16 = (0xF000 | (shift_bits << 8) | r3) as u16;
3636                bytes.extend_from_slice(&hw1.to_le_bytes());
3637                bytes.extend_from_slice(&hw2.to_le_bytes());
3638
3639                // ORR.W shift, shift, R12 (shift = (hi >> (n-32)) | (lo << (64-n)) = new_lo)
3640                let hw1: u16 = (0xEA40 | shift_bits) as u16;
3641                let hw2: u16 = ((shift_bits << 8) | r12) as u16;
3642                bytes.extend_from_slice(&hw1.to_le_bytes());
3643                bytes.extend_from_slice(&hw2.to_le_bytes());
3644
3645                // LSR.W rd_hi, rn_lo, R3 (rd_hi = lo >> (n-32), new_hi high bits)
3646                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3647                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | r3) as u16;
3648                bytes.extend_from_slice(&hw1.to_le_bytes());
3649                bytes.extend_from_slice(&hw2.to_le_bytes());
3650
3651                // ORR.W rd_hi, rd_hi, R4 (rd_hi = (lo >> (n-32)) | (hi << (64-n)) = new_hi)
3652                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3653                let hw2: u16 = ((rd_hi_bits << 8) | r4) as u16;
3654                bytes.extend_from_slice(&hw1.to_le_bytes());
3655                bytes.extend_from_slice(&hw2.to_le_bytes());
3656
3657                // MOV rd_lo, shift (rd_lo = new_lo)
3658                let d_bit = (rd_lo_bits >> 3) & 1;
3659                let mov_instr: u16 =
3660                    (0x4600 | (d_bit << 7) | (shift_bits << 3) | (rd_lo_bits & 0x7)) as u16;
3661                bytes.extend_from_slice(&mov_instr.to_le_bytes());
3662
3663                // POP {R4}
3664                bytes.extend_from_slice(&0xBC10u16.to_le_bytes());
3665
3666                Ok(bytes) // Total: 74 bytes
3667            }
3668
3669            // I64Clz: Count leading zeros in 64-bit value
3670            // If hi != 0: result = CLZ(hi)
3671            // If hi == 0: result = 32 + CLZ(lo)
3672            //
3673            // Layout (using CMP+BNE approach for consistency):
3674            // 0: CMP.W rnhi, #0 (4 bytes)
3675            // 4: BEQ .hi_zero (2 bytes) - branch forward to offset 14
3676            // 6: CLZ.W rd, rnhi (4 bytes)
3677            // 10: B .done (2 bytes) - branch forward to offset 22
3678            // 12: NOP (2 bytes) - padding for alignment
3679            // 14: .hi_zero: CLZ.W rd, rnlo (4 bytes)
3680            // 18: ADD.W rd, rd, #32 (4 bytes)
3681            // 22: .done
3682            ArmOp::I64Clz { rd, rnlo, rnhi } => {
3683                let rd_bits = reg_to_bits(rd);
3684                let rn_lo_bits = reg_to_bits(rnlo);
3685                let rn_hi_bits = reg_to_bits(rnhi);
3686                let mut bytes = Vec::new();
3687
3688                // CMP.W rnhi, #0 (4 bytes at offset 0)
3689                let hw1: u16 = (0xF1B0 | rn_hi_bits) as u16;
3690                let hw2: u16 = 0x0F00;
3691                bytes.extend_from_slice(&hw1.to_le_bytes());
3692                bytes.extend_from_slice(&hw2.to_le_bytes());
3693
3694                // BEQ .hi_zero (2 bytes at offset 4)
3695                // PC = 4 + 4 = 8, target = 14, offset = 6, imm8 = 3
3696                let beq: u16 = 0xD003;
3697                bytes.extend_from_slice(&beq.to_le_bytes());
3698
3699                // CLZ.W rd, rnhi (4 bytes at offset 6)
3700                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3701                let hw1: u16 = (0xFAB0 | rn_hi_bits) as u16;
3702                let hw2: u16 = (0xF080 | (rd_bits << 8) | rn_hi_bits) as u16;
3703                bytes.extend_from_slice(&hw1.to_le_bytes());
3704                bytes.extend_from_slice(&hw2.to_le_bytes());
3705
3706                // B .done (2 bytes at offset 10)
3707                // PC = 10 + 4 = 14, target = 22, offset = 8, imm11 = 4
3708                let b_done: u16 = 0xE004;
3709                bytes.extend_from_slice(&b_done.to_le_bytes());
3710
3711                // NOP (2 bytes at offset 12) - padding
3712                bytes.extend_from_slice(&0xBF00u16.to_le_bytes());
3713
3714                // .hi_zero: (offset 14)
3715                // CLZ.W rd, rnlo (4 bytes)
3716                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3717                let hw1: u16 = (0xFAB0 | rn_lo_bits) as u16;
3718                let hw2: u16 = (0xF080 | (rd_bits << 8) | rn_lo_bits) as u16;
3719                bytes.extend_from_slice(&hw1.to_le_bytes());
3720                bytes.extend_from_slice(&hw2.to_le_bytes());
3721
3722                // ADD.W rd, rd, #32 (4 bytes at offset 18)
3723                let hw1: u16 = (0xF100 | rd_bits) as u16;
3724                let hw2: u16 = ((rd_bits << 8) | 0x20) as u16;
3725                bytes.extend_from_slice(&hw1.to_le_bytes());
3726                bytes.extend_from_slice(&hw2.to_le_bytes());
3727
3728                // .done: (offset 22)
3729                // i64.clz returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
3730                // MOVS Rn, #0: 0010 0 Rn 00000000
3731                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
3732                bytes.extend_from_slice(&mov0.to_le_bytes());
3733
3734                Ok(bytes)
3735            }
3736
3737            // I64Ctz: Count trailing zeros in 64-bit value
3738            // If lo != 0: result = CTZ(lo) = CLZ(RBIT(lo))
3739            // If lo == 0: result = 32 + CTZ(hi) = 32 + CLZ(RBIT(hi))
3740            //
3741            // Layout:
3742            // 0: CMP.W rnlo, #0 (4 bytes)
3743            // 4: BEQ .lo_zero (2 bytes) - branch to offset 18
3744            // 6: RBIT.W rd, rnlo (4 bytes)
3745            // 10: CLZ.W rd, rd (4 bytes)
3746            // 14: B .done (2 bytes) - branch to offset 30
3747            // 16: NOP (2 bytes) - padding
3748            // 18: .lo_zero: RBIT.W rd, rnhi (4 bytes)
3749            // 22: CLZ.W rd, rd (4 bytes)
3750            // 26: ADD.W rd, rd, #32 (4 bytes)
3751            // 30: .done
3752            ArmOp::I64Ctz { rd, rnlo, rnhi } => {
3753                let rd_bits = reg_to_bits(rd);
3754                let rn_lo_bits = reg_to_bits(rnlo);
3755                let rn_hi_bits = reg_to_bits(rnhi);
3756                let mut bytes = Vec::new();
3757
3758                // CMP.W rnlo, #0 (4 bytes at offset 0)
3759                let hw1: u16 = (0xF1B0 | rn_lo_bits) as u16;
3760                let hw2: u16 = 0x0F00;
3761                bytes.extend_from_slice(&hw1.to_le_bytes());
3762                bytes.extend_from_slice(&hw2.to_le_bytes());
3763
3764                // BEQ .lo_zero (2 bytes at offset 4)
3765                // PC = 4 + 4 = 8, target = 18, offset = 10, imm8 = 5
3766                let beq: u16 = 0xD005;
3767                bytes.extend_from_slice(&beq.to_le_bytes());
3768
3769                // RBIT.W rd, rnlo (4 bytes at offset 6)
3770                // RBIT T1: hw1 = 0xFA9<Rm>, hw2 = 0xF<Rd>A<Rm>
3771                let hw1: u16 = (0xFA90 | rn_lo_bits) as u16;
3772                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rn_lo_bits) as u16;
3773                bytes.extend_from_slice(&hw1.to_le_bytes());
3774                bytes.extend_from_slice(&hw2.to_le_bytes());
3775
3776                // CLZ.W rd, rd (4 bytes at offset 10)
3777                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3778                let hw1: u16 = (0xFAB0 | rd_bits) as u16;
3779                let hw2: u16 = (0xF080 | (rd_bits << 8) | rd_bits) as u16;
3780                bytes.extend_from_slice(&hw1.to_le_bytes());
3781                bytes.extend_from_slice(&hw2.to_le_bytes());
3782
3783                // B .done (2 bytes at offset 14)
3784                // PC = 14 + 4 = 18, target = 30, offset = 12, imm11 = 6
3785                let b_done: u16 = 0xE006;
3786                bytes.extend_from_slice(&b_done.to_le_bytes());
3787
3788                // NOP (2 bytes at offset 16) - padding
3789                bytes.extend_from_slice(&0xBF00u16.to_le_bytes());
3790
3791                // .lo_zero: (offset 18)
3792                // RBIT.W rd, rnhi (4 bytes)
3793                // RBIT T1: hw1 = 0xFA9<Rm>, hw2 = 0xF<Rd>A<Rm>
3794                let hw1: u16 = (0xFA90 | rn_hi_bits) as u16;
3795                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rn_hi_bits) as u16;
3796                bytes.extend_from_slice(&hw1.to_le_bytes());
3797                bytes.extend_from_slice(&hw2.to_le_bytes());
3798
3799                // CLZ.W rd, rd (4 bytes at offset 22)
3800                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3801                let hw1: u16 = (0xFAB0 | rd_bits) as u16;
3802                let hw2: u16 = (0xF080 | (rd_bits << 8) | rd_bits) as u16;
3803                bytes.extend_from_slice(&hw1.to_le_bytes());
3804                bytes.extend_from_slice(&hw2.to_le_bytes());
3805
3806                // ADD.W rd, rd, #32 (4 bytes at offset 26)
3807                let hw1: u16 = (0xF100 | rd_bits) as u16;
3808                let hw2: u16 = ((rd_bits << 8) | 0x20) as u16;
3809                bytes.extend_from_slice(&hw1.to_le_bytes());
3810                bytes.extend_from_slice(&hw2.to_le_bytes());
3811
3812                // .done: (offset 30)
3813                // i64.ctz returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
3814                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
3815                bytes.extend_from_slice(&mov0.to_le_bytes());
3816
3817                Ok(bytes)
3818            }
3819
3820            // I64Popcnt: Population count of 64-bit value
3821            // result = POPCNT(lo) + POPCNT(hi)
3822            // Using SIMD-style parallel bit counting algorithm
3823            ArmOp::I64Popcnt { rd, rnlo, rnhi } => {
3824                let rd_bits = reg_to_bits(rd);
3825                let rn_lo_bits = reg_to_bits(rnlo);
3826                let rn_hi_bits = reg_to_bits(rnhi);
3827                let r12: u32 = 12; // IP scratch
3828                let r3: u32 = 3; // Scratch for hi popcnt result
3829                let mut bytes = Vec::new();
3830
3831                // PUSH {R3, R4, R5} - save scratch registers
3832                bytes.extend_from_slice(&0xB438u16.to_le_bytes());
3833
3834                // Strategy: compute popcnt(lo) -> R4, popcnt(hi) -> R5, add them -> rd
3835                // Using lookup table approach for each byte would be too large
3836                // Using shift-and-add approach instead
3837
3838                // For simplicity and correctness, use the efficient parallel algorithm
3839                // but implement it as a series of inline operations
3840
3841                // MOV R4, rnlo
3842                let d_bit: u32 = 0; // R4 < 8, so high bit is 0
3843                let mov: u16 = (0x4600 | (d_bit << 7) | (rn_lo_bits << 3) | (4 & 0x7)) as u16;
3844                bytes.extend_from_slice(&mov.to_le_bytes());
3845
3846                // MOV R5, rnhi
3847                let d_bit: u32 = 0; // R5 < 8, so high bit is 0
3848                let mov: u16 = (0x4600 | (d_bit << 7) | (rn_hi_bits << 3) | (5 & 0x7)) as u16;
3849                bytes.extend_from_slice(&mov.to_le_bytes());
3850
3851                // --- POPCNT for R4 (lo word) ---
3852                // Step 1: x = x - ((x >> 1) & 0x55555555)
3853                // LSR.W R12, R4, #1
3854                let hw1: u16 = 0xEA4F;
3855                let hw2: u16 = ((r12 << 8) | 0x50 | 4) as u16;
3856                bytes.extend_from_slice(&hw1.to_le_bytes());
3857                bytes.extend_from_slice(&hw2.to_le_bytes());
3858
3859                // Load 0x55555555 into R3 using MOVW/MOVT
3860                // MOVW R3, #0x5555
3861                bytes.extend_from_slice(&0xF245u16.to_le_bytes());
3862                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3863                // MOVT R3, #0x5555
3864                bytes.extend_from_slice(&0xF2C5u16.to_le_bytes());
3865                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3866
3867                // AND.W R12, R12, R3
3868                let hw1: u16 = (0xEA00 | r12) as u16;
3869                let hw2: u16 = ((r12 << 8) | r3) as u16;
3870                bytes.extend_from_slice(&hw1.to_le_bytes());
3871                bytes.extend_from_slice(&hw2.to_le_bytes());
3872
3873                // SUB.W R4, R4, R12
3874                let hw1: u16 = (0xEBA0 | 4) as u16;
3875                let hw2: u16 = ((4 << 8) | r12) as u16;
3876                bytes.extend_from_slice(&hw1.to_le_bytes());
3877                bytes.extend_from_slice(&hw2.to_le_bytes());
3878
3879                // Step 2: x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
3880                // Load 0x33333333 into R3
3881                // MOVW R3, #0x3333
3882                bytes.extend_from_slice(&0xF243u16.to_le_bytes());
3883                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3884                // MOVT R3, #0x3333
3885                bytes.extend_from_slice(&0xF2C3u16.to_le_bytes());
3886                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3887
3888                // AND.W R12, R4, R3
3889                let hw1: u16 = (0xEA00 | 4) as u16;
3890                let hw2: u16 = ((r12 << 8) | r3) as u16;
3891                bytes.extend_from_slice(&hw1.to_le_bytes());
3892                bytes.extend_from_slice(&hw2.to_le_bytes());
3893
3894                // LSR.W R4, R4, #2
3895                let hw1: u16 = 0xEA4F;
3896                let hw2: u16 = ((4 << 8) | 0x90 | 4) as u16;
3897                bytes.extend_from_slice(&hw1.to_le_bytes());
3898                bytes.extend_from_slice(&hw2.to_le_bytes());
3899
3900                // AND.W R4, R4, R3
3901                let hw1: u16 = (0xEA00 | 4) as u16;
3902                let hw2: u16 = ((4 << 8) | r3) as u16;
3903                bytes.extend_from_slice(&hw1.to_le_bytes());
3904                bytes.extend_from_slice(&hw2.to_le_bytes());
3905
3906                // ADD.W R4, R4, R12
3907                let hw1: u16 = (0xEB00 | 4) as u16;
3908                let hw2: u16 = ((4 << 8) | r12) as u16;
3909                bytes.extend_from_slice(&hw1.to_le_bytes());
3910                bytes.extend_from_slice(&hw2.to_le_bytes());
3911
3912                // Step 3: x = (x + (x >> 4)) & 0x0F0F0F0F
3913                // LSR.W R12, R4, #4
3914                // hw2 = (imm3 << 12) | (Rd << 8) | (imm2 << 6) | (type << 4) | Rm
3915                // imm5=4=00100 → imm3=1, imm2=0, type=01(LSR)
3916                let hw1: u16 = 0xEA4F;
3917                let hw2: u16 = (0x1000 | (r12 << 8) | 0x10 | 4) as u16;
3918                bytes.extend_from_slice(&hw1.to_le_bytes());
3919                bytes.extend_from_slice(&hw2.to_le_bytes());
3920
3921                // ADD.W R4, R4, R12
3922                let hw1: u16 = (0xEB00 | 4) as u16;
3923                let hw2: u16 = ((4 << 8) | r12) as u16;
3924                bytes.extend_from_slice(&hw1.to_le_bytes());
3925                bytes.extend_from_slice(&hw2.to_le_bytes());
3926
3927                // Load 0x0F0F0F0F into R3
3928                // MOVW R3, #0x0F0F (imm4=0, i=1, imm3=7, imm8=0x0F)
3929                // hw1 = 11110 1 10 0100 0000 = 0xF640
3930                // hw2 = 0 111 0011 00001111 = 0x730F
3931                bytes.extend_from_slice(&0xF640u16.to_le_bytes());
3932                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
3933                // MOVT R3, #0x0F0F
3934                bytes.extend_from_slice(&0xF6C0u16.to_le_bytes());
3935                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
3936
3937                // AND.W R4, R4, R3
3938                let hw1: u16 = (0xEA00 | 4) as u16;
3939                let hw2: u16 = ((4 << 8) | r3) as u16;
3940                bytes.extend_from_slice(&hw1.to_le_bytes());
3941                bytes.extend_from_slice(&hw2.to_le_bytes());
3942
3943                // Step 4: x = x * 0x01010101 >> 24
3944                // Load 0x01010101 into R3
3945                // MOVW R3, #0x0101
3946                bytes.extend_from_slice(&0xF240u16.to_le_bytes());
3947                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
3948                // MOVT R3, #0x0101
3949                bytes.extend_from_slice(&0xF2C0u16.to_le_bytes());
3950                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
3951
3952                // MUL R4, R4, R3
3953                // MUL T2: hw1 = 0xFB00|Rn, hw2 = 0xF000|(Rd<<8)|Rm
3954                let hw1: u16 = (0xFB00 | 4) as u16;
3955                let hw2: u16 = (0xF000 | (4 << 8) | r3) as u16;
3956                bytes.extend_from_slice(&hw1.to_le_bytes());
3957                bytes.extend_from_slice(&hw2.to_le_bytes());
3958
3959                // LSR.W R4, R4, #24
3960                // imm5=24=11000 → imm3=6, imm2=0, type=01(LSR)
3961                let hw1: u16 = 0xEA4F;
3962                let hw2: u16 = (0x6000 | (4 << 8) | 0x10 | 4) as u16;
3963                bytes.extend_from_slice(&hw1.to_le_bytes());
3964                bytes.extend_from_slice(&hw2.to_le_bytes());
3965
3966                // --- POPCNT for R5 (hi word) - same algorithm ---
3967                // Step 1
3968                let hw1: u16 = 0xEA4F;
3969                let hw2: u16 = ((r12 << 8) | 0x50 | 5) as u16;
3970                bytes.extend_from_slice(&hw1.to_le_bytes());
3971                bytes.extend_from_slice(&hw2.to_le_bytes());
3972
3973                // Load 0x55555555 into R3
3974                bytes.extend_from_slice(&0xF245u16.to_le_bytes());
3975                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3976                bytes.extend_from_slice(&0xF2C5u16.to_le_bytes());
3977                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3978
3979                let hw1: u16 = (0xEA00 | r12) as u16;
3980                let hw2: u16 = ((r12 << 8) | r3) as u16;
3981                bytes.extend_from_slice(&hw1.to_le_bytes());
3982                bytes.extend_from_slice(&hw2.to_le_bytes());
3983
3984                let hw1: u16 = (0xEBA0 | 5) as u16;
3985                let hw2: u16 = ((5 << 8) | r12) as u16;
3986                bytes.extend_from_slice(&hw1.to_le_bytes());
3987                bytes.extend_from_slice(&hw2.to_le_bytes());
3988
3989                // Step 2
3990                bytes.extend_from_slice(&0xF243u16.to_le_bytes());
3991                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3992                bytes.extend_from_slice(&0xF2C3u16.to_le_bytes());
3993                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3994
3995                let hw1: u16 = (0xEA00 | 5) as u16;
3996                let hw2: u16 = ((r12 << 8) | r3) as u16;
3997                bytes.extend_from_slice(&hw1.to_le_bytes());
3998                bytes.extend_from_slice(&hw2.to_le_bytes());
3999
4000                let hw1: u16 = 0xEA4F;
4001                let hw2: u16 = ((5 << 8) | 0x90 | 5) as u16;
4002                bytes.extend_from_slice(&hw1.to_le_bytes());
4003                bytes.extend_from_slice(&hw2.to_le_bytes());
4004
4005                let hw1: u16 = (0xEA00 | 5) as u16;
4006                let hw2: u16 = ((5 << 8) | r3) as u16;
4007                bytes.extend_from_slice(&hw1.to_le_bytes());
4008                bytes.extend_from_slice(&hw2.to_le_bytes());
4009
4010                let hw1: u16 = (0xEB00 | 5) as u16;
4011                let hw2: u16 = ((5 << 8) | r12) as u16;
4012                bytes.extend_from_slice(&hw1.to_le_bytes());
4013                bytes.extend_from_slice(&hw2.to_le_bytes());
4014
4015                // Step 3: LSR.W R12, R5, #4
4016                // imm5=4=00100 → imm3=1, imm2=0, type=01(LSR)
4017                let hw1: u16 = 0xEA4F;
4018                let hw2: u16 = (0x1000 | (r12 << 8) | 0x10 | 5) as u16;
4019                bytes.extend_from_slice(&hw1.to_le_bytes());
4020                bytes.extend_from_slice(&hw2.to_le_bytes());
4021
4022                let hw1: u16 = (0xEB00 | 5) as u16;
4023                let hw2: u16 = ((5 << 8) | r12) as u16;
4024                bytes.extend_from_slice(&hw1.to_le_bytes());
4025                bytes.extend_from_slice(&hw2.to_le_bytes());
4026
4027                // Load 0x0F0F0F0F into R3 (for hi-word)
4028                bytes.extend_from_slice(&0xF640u16.to_le_bytes());
4029                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
4030                bytes.extend_from_slice(&0xF6C0u16.to_le_bytes());
4031                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
4032
4033                let hw1: u16 = (0xEA00 | 5) as u16;
4034                let hw2: u16 = ((5 << 8) | r3) as u16;
4035                bytes.extend_from_slice(&hw1.to_le_bytes());
4036                bytes.extend_from_slice(&hw2.to_le_bytes());
4037
4038                // Step 4
4039                bytes.extend_from_slice(&0xF240u16.to_le_bytes());
4040                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
4041                bytes.extend_from_slice(&0xF2C0u16.to_le_bytes());
4042                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
4043
4044                // MUL R5, R5, R3
4045                // MUL T2: hw1 = 0xFB00|Rn, hw2 = 0xF000|(Rd<<8)|Rm
4046                let hw1: u16 = (0xFB00 | 5) as u16;
4047                let hw2: u16 = (0xF000 | (5 << 8) | r3) as u16;
4048                bytes.extend_from_slice(&hw1.to_le_bytes());
4049                bytes.extend_from_slice(&hw2.to_le_bytes());
4050
4051                // LSR.W R5, R5, #24
4052                // imm5=24=11000 → imm3=6, imm2=0, type=01(LSR)
4053                let hw1: u16 = 0xEA4F;
4054                let hw2: u16 = (0x6000 | (5 << 8) | 0x10 | 5) as u16;
4055                bytes.extend_from_slice(&hw1.to_le_bytes());
4056                bytes.extend_from_slice(&hw2.to_le_bytes());
4057
4058                // ADD rd, R4, R5 (combine lo and hi counts)
4059                // ADDS Rd, Rn, Rm (T1): 0001 100 Rm Rn Rd = 0x1800 | (Rm<<6) | (Rn<<3) | Rd
4060                let rd_bits_u16 = rd_bits as u16;
4061                let instr: u16 = 0x1800 | (5 << 6) | (4 << 3) | rd_bits_u16;
4062                bytes.extend_from_slice(&instr.to_le_bytes());
4063
4064                // POP {R3, R4, R5}
4065                bytes.extend_from_slice(&0xBC38u16.to_le_bytes());
4066
4067                // i64.popcnt returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
4068                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
4069                bytes.extend_from_slice(&mov0.to_le_bytes());
4070
4071                Ok(bytes)
4072            }
4073
4074            // I64Extend8S: Sign-extend low 8 bits to 64 bits
4075            // Result: rdlo = sign_extend_8(rnlo), rdhi = rdlo >> 31
4076            ArmOp::I64Extend8S { rdlo, rdhi, rnlo } => {
4077                let rdlo_bits = reg_to_bits(rdlo);
4078                let rdhi_bits = reg_to_bits(rdhi);
4079                let rnlo_bits = reg_to_bits(rnlo);
4080                let mut bytes = Vec::new();
4081
4082                // SXTB.W rdlo, rnlo (sign-extend byte to 32-bit)
4083                // SXTB T2: hw1 = 0xFA4F, hw2 = 0xF0<Rd><Rm>
4084                let hw1: u16 = 0xFA4F_u16;
4085                let hw2: u16 = (0xF080 | (rdlo_bits << 8) | rnlo_bits) as u16;
4086                bytes.extend_from_slice(&hw1.to_le_bytes());
4087                bytes.extend_from_slice(&hw2.to_le_bytes());
4088
4089                // ASR.W rdhi, rdlo, #31 (sign-extend to high word)
4090                // ASR (immediate): hw1 = 0xEA4F, hw2 = imm3:Rd:imm2:type:Rm
4091                // For imm5=31: imm3=111, imm2=11, type=10 (ASR)
4092                // hw2 = (7 << 12) | (rdhi << 8) | (3 << 6) | (2 << 4) | rdlo
4093                let hw1: u16 = 0xEA4F;
4094                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rdlo_bits) as u16;
4095                bytes.extend_from_slice(&hw1.to_le_bytes());
4096                bytes.extend_from_slice(&hw2.to_le_bytes());
4097
4098                Ok(bytes)
4099            }
4100
4101            // I64Extend16S: Sign-extend low 16 bits to 64 bits
4102            // Result: rdlo = sign_extend_16(rnlo), rdhi = rdlo >> 31
4103            ArmOp::I64Extend16S { rdlo, rdhi, rnlo } => {
4104                let rdlo_bits = reg_to_bits(rdlo);
4105                let rdhi_bits = reg_to_bits(rdhi);
4106                let rnlo_bits = reg_to_bits(rnlo);
4107                let mut bytes = Vec::new();
4108
4109                // SXTH.W rdlo, rnlo (sign-extend halfword to 32-bit)
4110                // SXTH T2: hw1 = 0xFA0F, hw2 = 0xF0<Rd><Rm>
4111                let hw1: u16 = 0xFA0F_u16;
4112                let hw2: u16 = (0xF080 | (rdlo_bits << 8) | rnlo_bits) as u16;
4113                bytes.extend_from_slice(&hw1.to_le_bytes());
4114                bytes.extend_from_slice(&hw2.to_le_bytes());
4115
4116                // ASR.W rdhi, rdlo, #31 (sign-extend to high word)
4117                let hw1: u16 = 0xEA4F;
4118                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rdlo_bits) as u16;
4119                bytes.extend_from_slice(&hw1.to_le_bytes());
4120                bytes.extend_from_slice(&hw2.to_le_bytes());
4121
4122                Ok(bytes)
4123            }
4124
4125            // I64Extend32S: Sign-extend low 32 bits to 64 bits
4126            // Result: rdlo = rnlo, rdhi = rnlo >> 31
4127            ArmOp::I64Extend32S { rdlo, rdhi, rnlo } => {
4128                let rdlo_bits = reg_to_bits(rdlo);
4129                let rdhi_bits = reg_to_bits(rdhi);
4130                let rnlo_bits = reg_to_bits(rnlo);
4131                let mut bytes = Vec::new();
4132
4133                // MOV rdlo, rnlo (if different)
4134                if rdlo_bits != rnlo_bits {
4135                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4136                    let d_bit = ((rdlo_bits >> 3) & 1) as u16;
4137                    let mov: u16 = 0x4600
4138                        | (d_bit << 7)
4139                        | ((rnlo_bits as u16) << 3)
4140                        | ((rdlo_bits & 0x7) as u16);
4141                    bytes.extend_from_slice(&mov.to_le_bytes());
4142                }
4143
4144                // ASR.W rdhi, rnlo, #31 (sign-extend to high word)
4145                let hw1: u16 = 0xEA4F;
4146                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rnlo_bits) as u16;
4147                bytes.extend_from_slice(&hw1.to_le_bytes());
4148                bytes.extend_from_slice(&hw2.to_le_bytes());
4149
4150                Ok(bytes)
4151            }
4152
4153            // SelectMove: IT <cond>; MOV{cond} rd, rm
4154            // Conditional move: only execute MOV if condition is true
4155            ArmOp::SelectMove { rd, rm, cond } => {
4156                let rd_bits = reg_to_bits(rd) as u16;
4157                let rm_bits = reg_to_bits(rm) as u16;
4158
4159                // Condition code encoding for IT block
4160                use synth_synthesis::Condition;
4161                let cond_bits: u16 = match cond {
4162                    Condition::EQ => 0x0, // Equal
4163                    Condition::NE => 0x1, // Not equal
4164                    Condition::HS => 0x2, // Higher or same (unsigned >=)
4165                    Condition::LO => 0x3, // Lower (unsigned <)
4166                    Condition::HI => 0x8, // Higher (unsigned >)
4167                    Condition::LS => 0x9, // Lower or same (unsigned <=)
4168                    Condition::GE => 0xA, // Greater or equal (signed)
4169                    Condition::LT => 0xB, // Less than (signed)
4170                    Condition::GT => 0xC, // Greater than (signed)
4171                    Condition::LE => 0xD, // Less or equal (signed)
4172                };
4173
4174                // IT <cond>: single Then block (mask = 0x8 for T only)
4175                // IT instruction: 1011 1111 firstcond mask
4176                let it_instr: u16 = 0xBF00 | (cond_bits << 4) | 0x8;
4177
4178                // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4179                // This MOV will only execute if condition is true due to IT block
4180                let d_bit = (rd_bits >> 3) & 1;
4181                let mov_instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
4182
4183                // Emit: IT <cond>, MOV rd, rm
4184                let mut bytes = it_instr.to_le_bytes().to_vec();
4185                bytes.extend_from_slice(&mov_instr.to_le_bytes());
4186                Ok(bytes)
4187            }
4188
4189            // Popcnt: Population count (count set bits)
4190            // ARM Cortex-M has no native POPCNT, so we implement the bit manipulation algorithm:
4191            // x = x - ((x >> 1) & 0x55555555);
4192            // x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
4193            // x = (x + (x >> 4)) & 0x0F0F0F0F;
4194            // x = x + (x >> 8);
4195            // x = x + (x >> 16);
4196            // return x & 0x3F;
4197            //
4198            // Uses rd as working register and R12 as scratch for constants
4199            ArmOp::Popcnt { rd, rm } => {
4200                let mut bytes = Vec::new();
4201
4202                // First, move rm to rd if they're different
4203                if rd != rm {
4204                    let rd_bits = reg_to_bits(rd) as u16;
4205                    let rm_bits = reg_to_bits(rm) as u16;
4206                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4207                    let d_bit = (rd_bits >> 3) & 1;
4208                    let mov_instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
4209                    bytes.extend_from_slice(&mov_instr.to_le_bytes());
4210                }
4211
4212                // Step 1: x = x - ((x >> 1) & 0x55555555)
4213                // Load 0x55555555 into R12
4214                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x5555)?);
4215                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x5555)?);
4216
4217                // R12_temp = rd >> 1
4218                // We need a second scratch register. Use R11.
4219                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 1)?);
4220
4221                // R11 = R11 & R12 (R11 = (x >> 1) & 0x55555555)
4222                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(11, 11, 12)?);
4223
4224                // rd = rd - R11
4225                bytes.extend_from_slice(&self.encode_thumb32_sub_reg_raw(
4226                    reg_to_bits(rd),
4227                    reg_to_bits(rd),
4228                    11,
4229                )?);
4230
4231                // Step 2: x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
4232                // Load 0x33333333 into R12
4233                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x3333)?);
4234                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x3333)?);
4235
4236                // R11 = rd & R12
4237                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4238                    11,
4239                    reg_to_bits(rd),
4240                    12,
4241                )?);
4242
4243                // rd = rd >> 2
4244                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(
4245                    reg_to_bits(rd),
4246                    reg_to_bits(rd),
4247                    2,
4248                )?);
4249
4250                // rd = rd & R12
4251                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4252                    reg_to_bits(rd),
4253                    reg_to_bits(rd),
4254                    12,
4255                )?);
4256
4257                // rd = rd + R11
4258                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4259                    reg_to_bits(rd),
4260                    reg_to_bits(rd),
4261                    11,
4262                )?);
4263
4264                // Step 3: x = (x + (x >> 4)) & 0x0F0F0F0F
4265                // R11 = rd >> 4
4266                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 4)?);
4267
4268                // rd = rd + R11
4269                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4270                    reg_to_bits(rd),
4271                    reg_to_bits(rd),
4272                    11,
4273                )?);
4274
4275                // Load 0x0F0F0F0F into R12
4276                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x0F0F)?);
4277                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x0F0F)?);
4278
4279                // rd = rd & R12
4280                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4281                    reg_to_bits(rd),
4282                    reg_to_bits(rd),
4283                    12,
4284                )?);
4285
4286                // Step 4: x = x + (x >> 8)
4287                // R11 = rd >> 8
4288                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 8)?);
4289
4290                // rd = rd + R11
4291                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4292                    reg_to_bits(rd),
4293                    reg_to_bits(rd),
4294                    11,
4295                )?);
4296
4297                // Step 5: x = x + (x >> 16)
4298                // R11 = rd >> 16
4299                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 16)?);
4300
4301                // rd = rd + R11
4302                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4303                    reg_to_bits(rd),
4304                    reg_to_bits(rd),
4305                    11,
4306                )?);
4307
4308                // Step 6: return x & 0x3F
4309                // AND with 0x3F (small immediate, can use BIC or AND with immediate)
4310                bytes.extend_from_slice(&self.encode_thumb32_and_imm_raw(
4311                    reg_to_bits(rd),
4312                    reg_to_bits(rd),
4313                    0x3F,
4314                )?);
4315
4316                Ok(bytes)
4317            }
4318
4319            // I64DivU: 64-bit unsigned division using binary long division
4320            // Input: R0:R1 = dividend, R2:R3 = divisor
4321            // Output: R0:R1 = quotient
4322            // Uses: R4-R7, R12 as loop counter (avoid R8 for Renode compatibility)
4323            ArmOp::I64DivU {
4324                rdlo: _,
4325                rdhi: _,
4326                rnlo: _,
4327                rnhi: _,
4328                rmlo: _,
4329                rmhi: _,
4330            } => {
4331                let mut bytes = Vec::new();
4332
4333                // PUSH {R4-R7} - save scratch registers (NO LR — this is inline code)
4334                // 16-bit PUSH: 1011 010 M rrrrrrrr where M=0 (no LR), r=R4-R7 = 0xF0
4335                // Encoding: 1011 0100 1111 0000 = 0xB4F0
4336                bytes.extend_from_slice(&0xB4F0u16.to_le_bytes());
4337
4338                // Initialize quotient (R4:R5) = 0
4339                bytes.extend_from_slice(&0x2400u16.to_le_bytes()); // MOV R4, #0
4340                bytes.extend_from_slice(&0x2500u16.to_le_bytes()); // MOV R5, #0
4341
4342                // Initialize remainder (R6:R7) = 0
4343                bytes.extend_from_slice(&0x2600u16.to_le_bytes()); // MOV R6, #0
4344                bytes.extend_from_slice(&0x2700u16.to_le_bytes()); // MOV R7, #0
4345
4346                // Initialize loop counter R12 = 64 (use R12 scratch instead of R8)
4347                // MOV.W R12, #64: F04F 0C40
4348                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4349                bytes.extend_from_slice(&0x0C40u16.to_le_bytes());
4350
4351                // Loop start
4352                let loop_start = bytes.len();
4353
4354                // === Loop body: process one bit ===
4355
4356                // 1. Shift quotient R4:R5 left by 1
4357                // LSLS R5, R5, #1 (16-bit: 0000 0010 1010 1101 = 0x006D -> actually 0x002D for LSL R5,R5,#1)
4358                // LSL Rd, Rm, #imm5: 000 00 imm5 Rm Rd = 000 00 00001 101 101 = 0x006D
4359                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4360                // Get carry from R4 into R5: ORR R5, R5, R4 LSR #31
4361                // Thumb-2 ORR with shifted register: EA45 75D4 = ORR.W R5, R5, R4, LSR #31
4362                // 11101010 010 S Rn | 0 imm3 Rd imm2 type Rm
4363                // type=01 (LSR), imm5=31 (imm3=111, imm2=11)
4364                bytes.extend_from_slice(&0xEA45u16.to_le_bytes());
4365                bytes.extend_from_slice(&0x75D4u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4366                // LSLS R4, R4, #1: 000 00 00001 100 100 = 0x0064
4367                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4368
4369                // 2. Shift remainder R6:R7 left by 1, OR in MSB of dividend R1
4370                // LSLS R7, R7, #1
4371                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4372                // ORR.W R7, R7, R6, LSR #31
4373                bytes.extend_from_slice(&0xEA47u16.to_le_bytes());
4374                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4375                // LSLS R6, R6, #1
4376                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4377                // ORR.W R6, R6, R1, LSR #31 (bring in MSB of dividend high)
4378                bytes.extend_from_slice(&0xEA46u16.to_le_bytes());
4379                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4380
4381                // 3. Shift dividend R0:R1 left by 1
4382                // LSLS R1, R1, #1
4383                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4384                // ORR.W R1, R1, R0, LSR #31
4385                bytes.extend_from_slice(&0xEA41u16.to_le_bytes());
4386                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4387                // LSLS R0, R0, #1
4388                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4389
4390                // 4. Compare remainder >= divisor (64-bit unsigned comparison)
4391                // Compare high words first: CMP R7, R3
4392                // CMP Rn, Rm encoding: 0x4280 | (Rm << 3) | Rn
4393                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3 (16-bit)
4394                // BHI means R7 > R3 (unsigned) - definitely subtract
4395                // BLO means R7 < R3 - definitely don't subtract
4396                // BEQ means need to check low words
4397
4398                // If high > divisor high: branch to subtract (forward +offset)
4399                // BHI.N +6 (skip CMP, skip BLO, do subtract)
4400                // BHI: 1101 1000 offset8 where cond=1000 (HI)
4401                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4 (to subtract block)
4402
4403                // If high < divisor high: branch past subtract
4404                // BLO.N +10 (skip to decrement)
4405                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BLO/BCC +12 (past subtract)
4406
4407                // High words equal, compare low: CMP R6, R2
4408                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2 (16-bit)
4409                // BLO/BCC past subtract (skip SUBS+SBC.W+ORR.W = 10 bytes = 4 halfwords from PC+4)
4410                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords (past subtract)
4411
4412                // === Subtract block: remainder -= divisor, quotient |= 1 ===
4413                // SUBS R6, R6, R2
4414                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2 (16-bit)
4415                // SBC R7, R7, R3 (with borrow)
4416                // Thumb-2 SBC.W: EB67 0703 = SBC.W R7, R7, R3
4417                bytes.extend_from_slice(&0xEB67u16.to_le_bytes());
4418                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4419                // ORR R4, R4, #1 (set bit 0 of quotient low)
4420                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4421                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4422
4423                // === Decrement counter and loop ===
4424                // SUBS.W R12, R12, #1 (decrement loop counter)
4425                // SUBS.W R12, R12, #1: F1BC 0C01
4426                bytes.extend_from_slice(&0xF1BCu16.to_le_bytes());
4427                bytes.extend_from_slice(&0x0C01u16.to_le_bytes());
4428
4429                // BNE back to loop_start
4430                let branch_offset_bytes = bytes.len() - loop_start + 4; // +4 for pipeline
4431                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4432                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4433                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4434
4435                // === Loop done, move quotient to R0:R1 ===
4436                bytes.extend_from_slice(&0x4620u16.to_le_bytes()); // MOV R0, R4
4437                bytes.extend_from_slice(&0x4629u16.to_le_bytes()); // MOV R1, R5
4438
4439                // POP {R4-R7} - restore scratch registers (NO PC — inline code continues)
4440                // 16-bit POP: 1011 110 P rrrrrrrr where P=0 (no PC), r=R4-R7 = 0xF0
4441                // Encoding: 1011 1100 1111 0000 = 0xBCF0
4442                bytes.extend_from_slice(&0xBCF0u16.to_le_bytes());
4443
4444                Ok(bytes)
4445            }
4446
4447            // I64DivS: 64-bit signed division
4448            // Converts to unsigned, divides, then applies sign
4449            // Input: R0:R1 = dividend (signed), R2:R3 = divisor (signed)
4450            // Output: R0:R1 = quotient (signed)
4451            ArmOp::I64DivS {
4452                rdlo: _,
4453                rdhi: _,
4454                rnlo: _,
4455                rnhi: _,
4456                rmlo: _,
4457                rmhi: _,
4458            } => {
4459                let mut bytes = Vec::new();
4460
4461                // PUSH {R4-R11} - save scratch registers (NO LR — inline code)
4462                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4463                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4464
4465                // Save result sign in R9: R9 = R1 XOR R3 (sign bit = MSB)
4466                // EOR.W R9, R1, R3
4467                bytes.extend_from_slice(&0xEA81u16.to_le_bytes());
4468                bytes.extend_from_slice(&0x0903u16.to_le_bytes());
4469
4470                // If dividend negative (R1 MSB set), negate it
4471                // TST R1, R1 (check sign)
4472                bytes.extend_from_slice(&0x4209u16.to_le_bytes()); // TST R1, R1
4473                // BPL skip_neg_dividend (+10 bytes = 5 halfwords)
4474                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4475
4476                // Negate R0:R1 (64-bit): RSBS R0, R0, #0; SBC R1, R1, R1 LSL #1
4477                // Actually: MVN R0, R0; MVN R1, R1; ADDS R0, R0, #1; ADC R1, R1, #0
4478                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4479                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4480                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4481                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4482                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4483
4484                // If divisor negative (R3 MSB set), negate it
4485                bytes.extend_from_slice(&0x421Bu16.to_le_bytes()); // TST R3, R3
4486                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4487
4488                // Negate R2:R3
4489                bytes.extend_from_slice(&0x43D2u16.to_le_bytes()); // MVNS R2, R2
4490                bytes.extend_from_slice(&0x43DBu16.to_le_bytes()); // MVNS R3, R3
4491                bytes.extend_from_slice(&0x1C52u16.to_le_bytes()); // ADDS R2, R2, #1
4492                bytes.extend_from_slice(&0xF143u16.to_le_bytes()); // ADC.W R3, R3, #0
4493                bytes.extend_from_slice(&0x0300u16.to_le_bytes());
4494
4495                // === Now do unsigned division (same as I64DivU) ===
4496                // Initialize quotient (R4:R5) = 0
4497                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4498                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4499                // Initialize remainder (R6:R7) = 0
4500                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4501                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4502                // Initialize loop counter R8 = 64
4503                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4504                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4505
4506                let loop_start = bytes.len();
4507
4508                // Shift quotient left
4509                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4510                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4511                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4512                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4513
4514                // Shift remainder left, OR in MSB of dividend
4515                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4516                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4517                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4518                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4519                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4520                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4521
4522                // Shift dividend left
4523                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4524                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4525                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4526                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4527
4528                // Compare and conditionally subtract
4529                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4530                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4531                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4532                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4533                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4534
4535                // Subtract and set quotient bit
4536                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4537                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4538                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4539                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4540                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4541
4542                // Decrement and loop
4543                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4544                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4545
4546                let branch_offset_bytes = bytes.len() - loop_start + 4;
4547                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4548                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4549                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4550
4551                // Move quotient to R0:R1
4552                bytes.extend_from_slice(&0x4620u16.to_le_bytes()); // MOV R0, R4
4553                bytes.extend_from_slice(&0x4629u16.to_le_bytes()); // MOV R1, R5
4554
4555                // If result should be negative (R9 MSB set), negate R0:R1
4556                bytes.extend_from_slice(&0xF1B9u16.to_le_bytes()); // TST.W R9, R9 (check MSB)
4557                bytes.extend_from_slice(&0x0F00u16.to_le_bytes());
4558                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8 (skip negation)
4559
4560                // Negate result R0:R1
4561                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4562                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4563                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4564                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4565                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4566
4567                // POP {R4-R11} - restore scratch registers (NO PC — inline code continues)
4568                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4569                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4570
4571                Ok(bytes)
4572            }
4573
4574            // I64RemU: 64-bit unsigned remainder using binary long division
4575            // Same algorithm as I64DivU but returns remainder instead of quotient
4576            // Input: R0:R1 = dividend, R2:R3 = divisor
4577            // Output: R0:R1 = remainder
4578            ArmOp::I64RemU {
4579                rdlo: _,
4580                rdhi: _,
4581                rnlo: _,
4582                rnhi: _,
4583                rmlo: _,
4584                rmhi: _,
4585            } => {
4586                let mut bytes = Vec::new();
4587
4588                // PUSH {R4-R8} - save scratch registers (NO LR — inline code)
4589                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4590                bytes.extend_from_slice(&0x01F0u16.to_le_bytes());
4591
4592                // Initialize quotient (R4:R5) = 0 (computed but not returned)
4593                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4594                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4595                // Initialize remainder (R6:R7) = 0
4596                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4597                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4598                // Initialize loop counter R8 = 64
4599                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4600                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4601
4602                let loop_start = bytes.len();
4603
4604                // Shift quotient left (not needed for result, but keeps algorithm same)
4605                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4606                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4607                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4608                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4609
4610                // Shift remainder left, OR in MSB of dividend
4611                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4612                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4613                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4614                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4615                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4616                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4617
4618                // Shift dividend left
4619                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4620                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4621                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4622                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4623
4624                // Compare and conditionally subtract
4625                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4626                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4627                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4628                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4629                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4630
4631                // Subtract and set quotient bit
4632                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4633                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4634                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4635                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4636                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4637
4638                // Decrement and loop
4639                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4640                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4641
4642                let branch_offset_bytes = bytes.len() - loop_start + 4;
4643                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4644                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4645                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4646
4647                // Move REMAINDER to R0:R1 (difference from I64DivU)
4648                bytes.extend_from_slice(&0x4630u16.to_le_bytes()); // MOV R0, R6
4649                bytes.extend_from_slice(&0x4639u16.to_le_bytes()); // MOV R1, R7
4650
4651                // POP {R4-R8} - restore scratch registers (NO PC — inline code continues)
4652                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4653                bytes.extend_from_slice(&0x01F0u16.to_le_bytes());
4654
4655                Ok(bytes)
4656            }
4657
4658            // I64RemS: 64-bit signed remainder
4659            // Remainder sign follows dividend sign (not quotient rule)
4660            // Input: R0:R1 = dividend (signed), R2:R3 = divisor (signed)
4661            // Output: R0:R1 = remainder (signed, same sign as dividend)
4662            ArmOp::I64RemS {
4663                rdlo: _,
4664                rdhi: _,
4665                rnlo: _,
4666                rnhi: _,
4667                rmlo: _,
4668                rmhi: _,
4669            } => {
4670                let mut bytes = Vec::new();
4671
4672                // PUSH {R4-R11} - save scratch registers (NO LR — inline code)
4673                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4674                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4675
4676                // Save dividend sign in R9 (remainder sign = dividend sign)
4677                // MOV R9, R1 (just need the sign bit)
4678                bytes.extend_from_slice(&0x4689u16.to_le_bytes()); // MOV R9, R1
4679
4680                // If dividend negative (R1 MSB set), negate it
4681                bytes.extend_from_slice(&0x4209u16.to_le_bytes()); // TST R1, R1
4682                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4683
4684                // Negate R0:R1
4685                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4686                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4687                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4688                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4689                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4690
4691                // If divisor negative (R3 MSB set), negate it
4692                bytes.extend_from_slice(&0x421Bu16.to_le_bytes()); // TST R3, R3
4693                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4694
4695                // Negate R2:R3
4696                bytes.extend_from_slice(&0x43D2u16.to_le_bytes()); // MVNS R2, R2
4697                bytes.extend_from_slice(&0x43DBu16.to_le_bytes()); // MVNS R3, R3
4698                bytes.extend_from_slice(&0x1C52u16.to_le_bytes()); // ADDS R2, R2, #1
4699                bytes.extend_from_slice(&0xF143u16.to_le_bytes()); // ADC.W R3, R3, #0
4700                bytes.extend_from_slice(&0x0300u16.to_le_bytes());
4701
4702                // === Unsigned division algorithm ===
4703                // Initialize quotient (R4:R5) = 0
4704                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4705                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4706                // Initialize remainder (R6:R7) = 0
4707                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4708                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4709                // Initialize loop counter R8 = 64
4710                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4711                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4712
4713                let loop_start = bytes.len();
4714
4715                // Shift quotient left
4716                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4717                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4718                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4719                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4720
4721                // Shift remainder left, OR in MSB of dividend
4722                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4723                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4724                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4725                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4726                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4727                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4728
4729                // Shift dividend left
4730                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4731                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4732                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4733                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4734
4735                // Compare and conditionally subtract
4736                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4737                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4738                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4739                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4740                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4741
4742                // Subtract and set quotient bit
4743                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4744                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4745                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4746                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4747                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4748
4749                // Decrement and loop
4750                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4751                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4752
4753                let branch_offset_bytes = bytes.len() - loop_start + 4;
4754                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4755                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4756                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4757
4758                // Move remainder to R0:R1
4759                bytes.extend_from_slice(&0x4630u16.to_le_bytes()); // MOV R0, R6
4760                bytes.extend_from_slice(&0x4639u16.to_le_bytes()); // MOV R1, R7
4761
4762                // If original dividend was negative (R9 MSB set), negate remainder
4763                bytes.extend_from_slice(&0xF1B9u16.to_le_bytes()); // TST.W R9, R9
4764                bytes.extend_from_slice(&0x0F00u16.to_le_bytes());
4765                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4766
4767                // Negate result R0:R1
4768                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4769                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4770                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4771                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4772                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4773
4774                // POP {R4-R11} - restore scratch registers (NO PC — inline code continues)
4775                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4776                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4777
4778                Ok(bytes)
4779            }
4780
4781            // === F32 VFP single-precision Thumb-2 encodings ===
4782            // VFP instruction words are identical to ARM32; emit as two LE halfwords.
4783            ArmOp::F32Add { sd, sn, sm } => {
4784                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE300A00, sd, sn, sm)?))
4785            }
4786            ArmOp::F32Sub { sd, sn, sm } => {
4787                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE300A40, sd, sn, sm)?))
4788            }
4789            ArmOp::F32Mul { sd, sn, sm } => {
4790                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE200A00, sd, sn, sm)?))
4791            }
4792            ArmOp::F32Div { sd, sn, sm } => {
4793                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE800A00, sd, sn, sm)?))
4794            }
4795            ArmOp::F32Abs { sd, sm } => {
4796                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB00AC0, sd, sm)?))
4797            }
4798            ArmOp::F32Neg { sd, sm } => {
4799                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB10A40, sd, sm)?))
4800            }
4801            ArmOp::F32Sqrt { sd, sm } => {
4802                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB10AC0, sd, sm)?))
4803            }
4804
4805            // f32 pseudo-ops — multi-instruction sequences
4806            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
4807            ArmOp::F32Ceil { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b01),
4808            ArmOp::F32Floor { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b10),
4809            ArmOp::F32Trunc { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b11),
4810            ArmOp::F32Nearest { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b00),
4811            ArmOp::F32Min { sd, sn, sm } => self.encode_thumb_f32_minmax(sd, sn, sm, true),
4812            ArmOp::F32Max { sd, sn, sm } => self.encode_thumb_f32_minmax(sd, sn, sm, false),
4813            ArmOp::F32Copysign { sd, sn, sm } => self.encode_thumb_f32_copysign(sd, sn, sm),
4814
4815            // f32 comparisons — VCMP + VMRS + MOV #0 + IT + MOV #1
4816            ArmOp::F32Eq { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x0),
4817            ArmOp::F32Ne { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x1),
4818            ArmOp::F32Lt { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x4),
4819            ArmOp::F32Le { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x9),
4820            ArmOp::F32Gt { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0xC),
4821            ArmOp::F32Ge { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0xA),
4822
4823            ArmOp::F32Const { sd, value } => self.encode_thumb_f32_const(sd, *value),
4824
4825            ArmOp::F32Load { sd, addr } => {
4826                Ok(vfp_to_thumb_bytes(encode_vfp_ldst(0xED900A00, sd, addr)?))
4827            }
4828            ArmOp::F32Store { sd, addr } => {
4829                Ok(vfp_to_thumb_bytes(encode_vfp_ldst(0xED800A00, sd, addr)?))
4830            }
4831
4832            ArmOp::F32ConvertI32S { sd, rm } => self.encode_thumb_f32_convert_i32(sd, rm, true),
4833            ArmOp::F32ConvertI32U { sd, rm } => self.encode_thumb_f32_convert_i32(sd, rm, false),
4834            ArmOp::F32ConvertI64S { .. } | ArmOp::F32ConvertI64U { .. } => {
4835                Err(synth_core::Error::synthesis(
4836                    "F32 i64 conversion not supported (requires register pairs on 32-bit ARM)",
4837                ))
4838            }
4839            ArmOp::F32ReinterpretI32 { sd, rm } => {
4840                Ok(vfp_to_thumb_bytes(encode_vmov_core_sreg(true, sd, rm)?))
4841            }
4842            ArmOp::I32ReinterpretF32 { rd, sm } => {
4843                Ok(vfp_to_thumb_bytes(encode_vmov_core_sreg(false, sm, rd)?))
4844            }
4845            ArmOp::I32TruncF32S { rd, sm } => self.encode_thumb_i32_trunc_f32(rd, sm, true),
4846            ArmOp::I32TruncF32U { rd, sm } => self.encode_thumb_i32_trunc_f32(rd, sm, false),
4847
4848            // === F64 VFP double-precision Thumb-2 encodings ===
4849            // VFP instruction words are identical to ARM32; emit as two LE halfwords.
4850            ArmOp::F64Add { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4851                0xEE300B00, dd, dn, dm,
4852            )?)),
4853            ArmOp::F64Sub { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4854                0xEE300B40, dd, dn, dm,
4855            )?)),
4856            ArmOp::F64Mul { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4857                0xEE200B00, dd, dn, dm,
4858            )?)),
4859            ArmOp::F64Div { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4860                0xEE800B00, dd, dn, dm,
4861            )?)),
4862            ArmOp::F64Abs { dd, dm } => {
4863                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB00BC0, dd, dm)?))
4864            }
4865            ArmOp::F64Neg { dd, dm } => {
4866                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB10B40, dd, dm)?))
4867            }
4868            ArmOp::F64Sqrt { dd, dm } => {
4869                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB10BC0, dd, dm)?))
4870            }
4871
4872            // f64 pseudo-ops
4873            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
4874            ArmOp::F64Ceil { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b01),
4875            ArmOp::F64Floor { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b10),
4876            ArmOp::F64Trunc { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b11),
4877            ArmOp::F64Nearest { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b00),
4878            ArmOp::F64Min { dd, dn, dm } => self.encode_thumb_f64_minmax(dd, dn, dm, true),
4879            ArmOp::F64Max { dd, dn, dm } => self.encode_thumb_f64_minmax(dd, dn, dm, false),
4880            ArmOp::F64Copysign { dd, dn, dm } => self.encode_thumb_f64_copysign(dd, dn, dm),
4881
4882            // f64 comparisons
4883            ArmOp::F64Eq { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x0),
4884            ArmOp::F64Ne { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x1),
4885            ArmOp::F64Lt { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x4),
4886            ArmOp::F64Le { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x9),
4887            ArmOp::F64Gt { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0xC),
4888            ArmOp::F64Ge { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0xA),
4889
4890            ArmOp::F64Const { dd, value } => self.encode_thumb_f64_const(dd, *value),
4891
4892            ArmOp::F64Load { dd, addr } => Ok(vfp_to_thumb_bytes(encode_vfp_ldst_f64(
4893                0xED900B00, dd, addr,
4894            )?)),
4895            ArmOp::F64Store { dd, addr } => Ok(vfp_to_thumb_bytes(encode_vfp_ldst_f64(
4896                0xED800B00, dd, addr,
4897            )?)),
4898
4899            ArmOp::F64ConvertI32S { dd, rm } => self.encode_thumb_f64_convert_i32(dd, rm, true),
4900            ArmOp::F64ConvertI32U { dd, rm } => self.encode_thumb_f64_convert_i32(dd, rm, false),
4901            ArmOp::F64ConvertI64S { .. } | ArmOp::F64ConvertI64U { .. } => {
4902                Err(synth_core::Error::synthesis(
4903                    "F64 i64 conversion not supported (requires register pairs on 32-bit ARM)",
4904                ))
4905            }
4906            ArmOp::F64PromoteF32 { dd, sm } => self.encode_thumb_f64_promote_f32(dd, sm),
4907            ArmOp::F64ReinterpretI64 { dd, rmlo, rmhi } => Ok(vfp_to_thumb_bytes(
4908                encode_vmov_core_dreg(true, dd, rmlo, rmhi)?,
4909            )),
4910            ArmOp::I64ReinterpretF64 { rdlo, rdhi, dm } => Ok(vfp_to_thumb_bytes(
4911                encode_vmov_core_dreg(false, dm, rdlo, rdhi)?,
4912            )),
4913            ArmOp::I64TruncF64S { .. } | ArmOp::I64TruncF64U { .. } => {
4914                Err(synth_core::Error::synthesis(
4915                    "i64 truncation from F64 not supported (requires i64 register pairs on 32-bit ARM)",
4916                ))
4917            }
4918            ArmOp::I32TruncF64S { rd, dm } => self.encode_thumb_i32_trunc_f64(rd, dm, true),
4919            ArmOp::I32TruncF64U { rd, dm } => self.encode_thumb_i32_trunc_f64(rd, dm, false),
4920
4921            // ===== i64 operations: encode as multi-instruction Thumb-2 sequences =====
4922
4923            // I64Add: ADDS rdlo, rnlo, rmlo; ADC.W rdhi, rnhi, rmhi
4924            ArmOp::I64Add {
4925                rdlo,
4926                rdhi,
4927                rnlo,
4928                rnhi,
4929                rmlo,
4930                rmhi,
4931            } => {
4932                let mut bytes = Vec::new();
4933                // ADDS rdlo, rnlo, rmlo (16-bit)
4934                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Adds {
4935                    rd: *rdlo,
4936                    rn: *rnlo,
4937                    op2: Operand2::Reg(*rmlo),
4938                })?);
4939                // ADC.W rdhi, rnhi, rmhi (32-bit)
4940                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Adc {
4941                    rd: *rdhi,
4942                    rn: *rnhi,
4943                    op2: Operand2::Reg(*rmhi),
4944                })?);
4945                Ok(bytes)
4946            }
4947
4948            // I64Sub: SUBS rdlo, rnlo, rmlo; SBC.W rdhi, rnhi, rmhi
4949            ArmOp::I64Sub {
4950                rdlo,
4951                rdhi,
4952                rnlo,
4953                rnhi,
4954                rmlo,
4955                rmhi,
4956            } => {
4957                let mut bytes = Vec::new();
4958                // SUBS rdlo, rnlo, rmlo (16-bit)
4959                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Subs {
4960                    rd: *rdlo,
4961                    rn: *rnlo,
4962                    op2: Operand2::Reg(*rmlo),
4963                })?);
4964                // SBC.W rdhi, rnhi, rmhi (32-bit)
4965                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Sbc {
4966                    rd: *rdhi,
4967                    rn: *rnhi,
4968                    op2: Operand2::Reg(*rmhi),
4969                })?);
4970                Ok(bytes)
4971            }
4972
4973            // I64And: AND rdlo, rnlo, rmlo; AND rdhi, rnhi, rmhi
4974            ArmOp::I64And {
4975                rdlo,
4976                rdhi,
4977                rnlo,
4978                rnhi,
4979                rmlo,
4980                rmhi,
4981            } => {
4982                let mut bytes = Vec::new();
4983                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::And {
4984                    rd: *rdlo,
4985                    rn: *rnlo,
4986                    op2: Operand2::Reg(*rmlo),
4987                })?);
4988                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::And {
4989                    rd: *rdhi,
4990                    rn: *rnhi,
4991                    op2: Operand2::Reg(*rmhi),
4992                })?);
4993                Ok(bytes)
4994            }
4995
4996            // I64Or: ORR rdlo, rnlo, rmlo; ORR rdhi, rnhi, rmhi
4997            ArmOp::I64Or {
4998                rdlo,
4999                rdhi,
5000                rnlo,
5001                rnhi,
5002                rmlo,
5003                rmhi,
5004            } => {
5005                let mut bytes = Vec::new();
5006                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Orr {
5007                    rd: *rdlo,
5008                    rn: *rnlo,
5009                    op2: Operand2::Reg(*rmlo),
5010                })?);
5011                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Orr {
5012                    rd: *rdhi,
5013                    rn: *rnhi,
5014                    op2: Operand2::Reg(*rmhi),
5015                })?);
5016                Ok(bytes)
5017            }
5018
5019            // I64Xor: EOR rdlo, rnlo, rmlo; EOR rdhi, rnhi, rmhi
5020            ArmOp::I64Xor {
5021                rdlo,
5022                rdhi,
5023                rnlo,
5024                rnhi,
5025                rmlo,
5026                rmhi,
5027            } => {
5028                let mut bytes = Vec::new();
5029                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Eor {
5030                    rd: *rdlo,
5031                    rn: *rnlo,
5032                    op2: Operand2::Reg(*rmlo),
5033                })?);
5034                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Eor {
5035                    rd: *rdhi,
5036                    rn: *rnhi,
5037                    op2: Operand2::Reg(*rmhi),
5038                })?);
5039                Ok(bytes)
5040            }
5041
5042            // I64Eqz: ORR scratch, lo, hi; ITE EQ; MOV rd, #1; MOV rd, #0
5043            ArmOp::I64Eqz { rd, rnlo, rnhi } => self.encode_thumb(&ArmOp::I64SetCondZ {
5044                rd: *rd,
5045                rn_lo: *rnlo,
5046                rn_hi: *rnhi,
5047            }),
5048
5049            // I64 comparisons: delegate to I64SetCond
5050            ArmOp::I64Eq {
5051                rd,
5052                rnlo,
5053                rnhi,
5054                rmlo,
5055                rmhi,
5056            } => self.encode_thumb(&ArmOp::I64SetCond {
5057                rd: *rd,
5058                rn_lo: *rnlo,
5059                rn_hi: *rnhi,
5060                rm_lo: *rmlo,
5061                rm_hi: *rmhi,
5062                cond: synth_synthesis::Condition::EQ,
5063            }),
5064
5065            ArmOp::I64Ne {
5066                rd,
5067                rnlo,
5068                rnhi,
5069                rmlo,
5070                rmhi,
5071            } => self.encode_thumb(&ArmOp::I64SetCond {
5072                rd: *rd,
5073                rn_lo: *rnlo,
5074                rn_hi: *rnhi,
5075                rm_lo: *rmlo,
5076                rm_hi: *rmhi,
5077                cond: synth_synthesis::Condition::NE,
5078            }),
5079
5080            ArmOp::I64LtS {
5081                rd,
5082                rnlo,
5083                rnhi,
5084                rmlo,
5085                rmhi,
5086            } => self.encode_thumb(&ArmOp::I64SetCond {
5087                rd: *rd,
5088                rn_lo: *rnlo,
5089                rn_hi: *rnhi,
5090                rm_lo: *rmlo,
5091                rm_hi: *rmhi,
5092                cond: synth_synthesis::Condition::LT,
5093            }),
5094
5095            ArmOp::I64LtU {
5096                rd,
5097                rnlo,
5098                rnhi,
5099                rmlo,
5100                rmhi,
5101            } => self.encode_thumb(&ArmOp::I64SetCond {
5102                rd: *rd,
5103                rn_lo: *rnlo,
5104                rn_hi: *rnhi,
5105                rm_lo: *rmlo,
5106                rm_hi: *rmhi,
5107                cond: synth_synthesis::Condition::LO,
5108            }),
5109
5110            ArmOp::I64LeS {
5111                rd,
5112                rnlo,
5113                rnhi,
5114                rmlo,
5115                rmhi,
5116            } => self.encode_thumb(&ArmOp::I64SetCond {
5117                rd: *rd,
5118                rn_lo: *rnlo,
5119                rn_hi: *rnhi,
5120                rm_lo: *rmlo,
5121                rm_hi: *rmhi,
5122                cond: synth_synthesis::Condition::LE,
5123            }),
5124
5125            ArmOp::I64LeU {
5126                rd,
5127                rnlo,
5128                rnhi,
5129                rmlo,
5130                rmhi,
5131            } => self.encode_thumb(&ArmOp::I64SetCond {
5132                rd: *rd,
5133                rn_lo: *rnlo,
5134                rn_hi: *rnhi,
5135                rm_lo: *rmlo,
5136                rm_hi: *rmhi,
5137                cond: synth_synthesis::Condition::LS,
5138            }),
5139
5140            ArmOp::I64GtS {
5141                rd,
5142                rnlo,
5143                rnhi,
5144                rmlo,
5145                rmhi,
5146            } => self.encode_thumb(&ArmOp::I64SetCond {
5147                rd: *rd,
5148                rn_lo: *rnlo,
5149                rn_hi: *rnhi,
5150                rm_lo: *rmlo,
5151                rm_hi: *rmhi,
5152                cond: synth_synthesis::Condition::GT,
5153            }),
5154
5155            ArmOp::I64GtU {
5156                rd,
5157                rnlo,
5158                rnhi,
5159                rmlo,
5160                rmhi,
5161            } => self.encode_thumb(&ArmOp::I64SetCond {
5162                rd: *rd,
5163                rn_lo: *rnlo,
5164                rn_hi: *rnhi,
5165                rm_lo: *rmlo,
5166                rm_hi: *rmhi,
5167                cond: synth_synthesis::Condition::HI,
5168            }),
5169
5170            ArmOp::I64GeS {
5171                rd,
5172                rnlo,
5173                rnhi,
5174                rmlo,
5175                rmhi,
5176            } => self.encode_thumb(&ArmOp::I64SetCond {
5177                rd: *rd,
5178                rn_lo: *rnlo,
5179                rn_hi: *rnhi,
5180                rm_lo: *rmlo,
5181                rm_hi: *rmhi,
5182                cond: synth_synthesis::Condition::GE,
5183            }),
5184
5185            ArmOp::I64GeU {
5186                rd,
5187                rnlo,
5188                rnhi,
5189                rmlo,
5190                rmhi,
5191            } => self.encode_thumb(&ArmOp::I64SetCond {
5192                rd: *rd,
5193                rn_lo: *rnlo,
5194                rn_hi: *rnhi,
5195                rm_lo: *rmlo,
5196                rm_hi: *rmhi,
5197                cond: synth_synthesis::Condition::HS,
5198            }),
5199
5200            // I64Const: MOVW rdlo, lo16; MOVT rdlo, hi16; MOVW rdhi, lo16_hi; MOVT rdhi, hi16_hi
5201            ArmOp::I64Const { rdlo, rdhi, value } => {
5202                let lo32 = *value as u32;
5203                let hi32 = (*value >> 32) as u32;
5204                let mut bytes = Vec::new();
5205                // Load low 32 bits into rdlo
5206                bytes.extend_from_slice(
5207                    &self.encode_thumb32_movw_raw(reg_to_bits(rdlo), lo32 & 0xFFFF)?,
5208                );
5209                if lo32 > 0xFFFF {
5210                    bytes.extend_from_slice(
5211                        &self.encode_thumb32_movt_raw(reg_to_bits(rdlo), lo32 >> 16)?,
5212                    );
5213                }
5214                // Load high 32 bits into rdhi
5215                bytes.extend_from_slice(
5216                    &self.encode_thumb32_movw_raw(reg_to_bits(rdhi), hi32 & 0xFFFF)?,
5217                );
5218                if hi32 > 0xFFFF {
5219                    bytes.extend_from_slice(
5220                        &self.encode_thumb32_movt_raw(reg_to_bits(rdhi), hi32 >> 16)?,
5221                    );
5222                }
5223                Ok(bytes)
5224            }
5225
5226            // I64Ldr: LDR rdlo, [base, offset]; LDR rdhi, [base, offset+4]
5227            ArmOp::I64Ldr { rdlo, rdhi, addr } => {
5228                let mut bytes = Vec::new();
5229                let offset = if addr.offset < 0 {
5230                    0u32
5231                } else {
5232                    addr.offset as u32
5233                };
5234                bytes.extend_from_slice(&self.encode_thumb32_ldr(rdlo, &addr.base, offset)?);
5235                bytes.extend_from_slice(&self.encode_thumb32_ldr(
5236                    rdhi,
5237                    &addr.base,
5238                    offset.wrapping_add(4),
5239                )?);
5240                Ok(bytes)
5241            }
5242
5243            // I64Str: STR rdlo, [base, offset]; STR rdhi, [base, offset+4]
5244            ArmOp::I64Str { rdlo, rdhi, addr } => {
5245                let mut bytes = Vec::new();
5246                let offset = if addr.offset < 0 {
5247                    0u32
5248                } else {
5249                    addr.offset as u32
5250                };
5251                bytes.extend_from_slice(&self.encode_thumb32_str(rdlo, &addr.base, offset)?);
5252                bytes.extend_from_slice(&self.encode_thumb32_str(
5253                    rdhi,
5254                    &addr.base,
5255                    offset.wrapping_add(4),
5256                )?);
5257                Ok(bytes)
5258            }
5259
5260            // I64ExtendI32S: MOV rdlo, rn; ASR rdhi, rdlo, #31 (sign-extend)
5261            ArmOp::I64ExtendI32S { rdlo, rdhi, rn } => {
5262                let mut bytes = Vec::new();
5263                if rdlo != rn {
5264                    // MOV rdlo, rn (16-bit)
5265                    bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Mov {
5266                        rd: *rdlo,
5267                        op2: Operand2::Reg(*rn),
5268                    })?);
5269                }
5270                // ASR rdhi, rdlo, #31 (sign-extend: fill high word with sign bit)
5271                bytes.extend_from_slice(
5272                    &self.encode_thumb32_shift(rdhi, rdlo, 31, 0b10)?, // ASR type
5273                );
5274                Ok(bytes)
5275            }
5276
5277            // I64ExtendI32U: MOV rdlo, rn; MOV rdhi, #0
5278            ArmOp::I64ExtendI32U { rdlo, rdhi, rn } => {
5279                let mut bytes = Vec::new();
5280                if rdlo != rn {
5281                    // MOV rdlo, rn
5282                    bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Mov {
5283                        rd: *rdlo,
5284                        op2: Operand2::Reg(*rn),
5285                    })?);
5286                }
5287                // MOV rdhi, #0 (16-bit: MOVS Rd, #0)
5288                let rdhi_bits = reg_to_bits(rdhi) as u16;
5289                let instr: u16 = 0x2000 | (rdhi_bits << 8);
5290                bytes.extend_from_slice(&instr.to_le_bytes());
5291                Ok(bytes)
5292            }
5293
5294            // I32WrapI64: MOV rd, rnlo (just take low 32 bits)
5295            ArmOp::I32WrapI64 { rd, rnlo } => {
5296                if rd == rnlo {
5297                    // No-op: already in the right register
5298                    let instr: u16 = 0xBF00; // NOP
5299                    Ok(instr.to_le_bytes().to_vec())
5300                } else {
5301                    // MOV rd, rnlo
5302                    self.encode_thumb(&ArmOp::Mov {
5303                        rd: *rd,
5304                        op2: Operand2::Reg(*rnlo),
5305                    })
5306                }
5307            }
5308
5309            // ===== Helium MVE operations (Thumb-2 encoding) =====
5310            ArmOp::MveLoad { qd, addr } => Ok(vfp_to_thumb_bytes(encode_mve_vldrw(qd, addr))),
5311            ArmOp::MveStore { qd, addr } => Ok(vfp_to_thumb_bytes(encode_mve_vstrw(qd, addr))),
5312            ArmOp::MveConst { qd, bytes } => self.encode_thumb_mve_const(qd, bytes),
5313            ArmOp::MveAnd { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5314                0xEF000150, qd, qn, qm,
5315            ))),
5316            ArmOp::MveOrr { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5317                0xEF200150, qd, qn, qm,
5318            ))),
5319            ArmOp::MveEor { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5320                0xFF000150, qd, qn, qm,
5321            ))),
5322            ArmOp::MveMvn { qd, qm } => {
5323                // VMVN Qd, Qm: 0xFFB005C0 | Qd<<12 | Qm
5324                let qd_enc = qreg_to_num(qd);
5325                let qm_enc = qreg_to_num(qm);
5326                let instr: u32 = 0xFFB005C0 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5327                Ok(vfp_to_thumb_bytes(instr))
5328            }
5329            ArmOp::MveBic { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5330                0xEF100150, qd, qn, qm,
5331            ))),
5332            ArmOp::MveAddI { qd, qn, qm, size } => {
5333                let sz = mve_size_bits(size);
5334                let base: u32 = 0xEF000840 | (sz << 20);
5335                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5336            }
5337            ArmOp::MveSubI { qd, qn, qm, size } => {
5338                let sz = mve_size_bits(size);
5339                let base: u32 = 0xFF000840 | (sz << 20);
5340                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5341            }
5342            ArmOp::MveMulI { qd, qn, qm, size } => {
5343                let sz = mve_size_bits(size);
5344                let base: u32 = 0xEF000950 | (sz << 20);
5345                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5346            }
5347            ArmOp::MveNegI { qd, qm, size } => {
5348                let sz = mve_size_bits(size);
5349                // VNEG.Sx Qd, Qm
5350                let qd_enc = qreg_to_num(qd);
5351                let qm_enc = qreg_to_num(qm);
5352                let base: u32 = 0xFFB103C0 | (sz << 18);
5353                let instr = base | ((qd_enc * 2) << 12) | (qm_enc * 2);
5354                Ok(vfp_to_thumb_bytes(instr))
5355            }
5356            ArmOp::MveDup { qd, rn, size } => {
5357                let sz = mve_size_bits(size);
5358                let qd_enc = qreg_to_num(qd);
5359                let rn_bits = reg_to_bits(rn);
5360                // VDUP.sz Qd, Rn: EEA0 0B10 variant
5361                // size encoding: 00=32, 01=16, 10=8
5362                let be = match sz {
5363                    0 => 0b00u32, // 8-bit
5364                    1 => 0b01,    // 16-bit
5365                    _ => 0b00,    // 32-bit (default)
5366                };
5367                let instr: u32 = 0xEEA00B10 | ((qd_enc * 2) << 16) | (rn_bits << 12) | (be << 5);
5368                Ok(vfp_to_thumb_bytes(instr))
5369            }
5370            ArmOp::MveExtractLane { rd, qn, lane, size } => {
5371                let qn_enc = qreg_to_num(qn);
5372                let rd_bits = reg_to_bits(rd);
5373                // VMOV.sz Rd, Dn[x] — extract from Q-register lane
5374                // For 32-bit: VMOV Rd, Dn — where Dn is the appropriate D-register
5375                let d_reg = qn_enc * 2 + ((*lane as u32) >> 1);
5376                let lane_in_d = (*lane as u32) & 1;
5377                let _sz = mve_size_bits(size);
5378                // VMOV Rd, Dn[x]: EE10 0B10 for 32-bit
5379                let instr: u32 = 0xEE100B10 | (d_reg << 16) | (rd_bits << 12) | (lane_in_d << 21);
5380                Ok(vfp_to_thumb_bytes(instr))
5381            }
5382            ArmOp::MveInsertLane { qd, rn, lane, size } => {
5383                let qd_enc = qreg_to_num(qd);
5384                let rn_bits = reg_to_bits(rn);
5385                let d_reg = qd_enc * 2 + ((*lane as u32) >> 1);
5386                let lane_in_d = (*lane as u32) & 1;
5387                let _sz = mve_size_bits(size);
5388                // VMOV Dn[x], Rn: EE00 0B10 for 32-bit
5389                let instr: u32 = 0xEE000B10 | (d_reg << 16) | (rn_bits << 12) | (lane_in_d << 21);
5390                Ok(vfp_to_thumb_bytes(instr))
5391            }
5392
5393            // MVE float comparisons — emit VCMP + VPSEL sequence (simplified: just VCMP)
5394            ArmOp::MveCmpEqI { qd, qn, qm, size }
5395            | ArmOp::MveCmpNeI { qd, qn, qm, size }
5396            | ArmOp::MveCmpLtS { qd, qn, qm, size }
5397            | ArmOp::MveCmpLtU { qd, qn, qm, size }
5398            | ArmOp::MveCmpGtS { qd, qn, qm, size }
5399            | ArmOp::MveCmpGtU { qd, qn, qm, size }
5400            | ArmOp::MveCmpLeS { qd, qn, qm, size }
5401            | ArmOp::MveCmpLeU { qd, qn, qm, size }
5402            | ArmOp::MveCmpGeS { qd, qn, qm, size }
5403            | ArmOp::MveCmpGeU { qd, qn, qm, size } => {
5404                // Encode as VADD (placeholder encoding — real implementation
5405                // would use VCMP + VPSEL pair)
5406                let sz = mve_size_bits(size);
5407                let base: u32 = 0xEF000840 | (sz << 20);
5408                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5409            }
5410
5411            // f32x4 MVE arithmetic
5412            ArmOp::MveAddF32 { qd, qn, qm } => {
5413                // VADD.F32 Qd, Qn, Qm (MVE): 0xEF000D40
5414                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF000D40, qd, qn, qm)))
5415            }
5416            ArmOp::MveSubF32 { qd, qn, qm } => {
5417                // VSUB.F32 Qd, Qn, Qm (MVE): 0xEF200D40
5418                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF200D40, qd, qn, qm)))
5419            }
5420            ArmOp::MveMulF32 { qd, qn, qm } => {
5421                // VMUL.F32 Qd, Qn, Qm (MVE): 0xFF000D50
5422                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xFF000D50, qd, qn, qm)))
5423            }
5424            ArmOp::MveNegF32 { qd, qm } => {
5425                let qd_enc = qreg_to_num(qd);
5426                let qm_enc = qreg_to_num(qm);
5427                // VNEG.F32 Qd, Qm: FFB907C0
5428                let instr: u32 = 0xFFB907C0 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5429                Ok(vfp_to_thumb_bytes(instr))
5430            }
5431            ArmOp::MveAbsF32 { qd, qm } => {
5432                let qd_enc = qreg_to_num(qd);
5433                let qm_enc = qreg_to_num(qm);
5434                // VABS.F32 Qd, Qm: FFB90740
5435                let instr: u32 = 0xFFB90740 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5436                Ok(vfp_to_thumb_bytes(instr))
5437            }
5438            ArmOp::MveCmpEqF32 { qd, qn, qm }
5439            | ArmOp::MveCmpNeF32 { qd, qn, qm }
5440            | ArmOp::MveCmpLtF32 { qd, qn, qm }
5441            | ArmOp::MveCmpLeF32 { qd, qn, qm }
5442            | ArmOp::MveCmpGtF32 { qd, qn, qm }
5443            | ArmOp::MveCmpGeF32 { qd, qn, qm } => {
5444                // Placeholder: encode as VADD.F32 (real impl needs VCMP.F32 + VPSEL)
5445                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF000D40, qd, qn, qm)))
5446            }
5447            ArmOp::MveDupF32 { qd, rn } => {
5448                let qd_enc = qreg_to_num(qd);
5449                let rn_bits = reg_to_bits(rn);
5450                // VDUP.32 Qd, Rn (same encoding as integer VDUP.32)
5451                let instr: u32 = 0xEEA00B10 | ((qd_enc * 2) << 16) | (rn_bits << 12);
5452                Ok(vfp_to_thumb_bytes(instr))
5453            }
5454            ArmOp::MveExtractLaneF32 { rd, qn, lane } => {
5455                let qn_enc = qreg_to_num(qn);
5456                let rd_bits = reg_to_bits(rd);
5457                // VMOV Rd, Sn where Sn = Q*4 + lane
5458                let s_num = qn_enc * 4 + (*lane as u32);
5459                let (vn, n) = encode_sreg(s_num);
5460                let instr: u32 = 0xEE100A10 | (vn << 16) | (rd_bits << 12) | (n << 7);
5461                Ok(vfp_to_thumb_bytes(instr))
5462            }
5463            ArmOp::MveReplaceLaneF32 { qd, rn, lane } => {
5464                let qd_enc = qreg_to_num(qd);
5465                let rn_bits = reg_to_bits(rn);
5466                // VMOV Sn, Rn where Sn = Q*4 + lane
5467                let s_num = qd_enc * 4 + (*lane as u32);
5468                let (vn, n) = encode_sreg(s_num);
5469                let instr: u32 = 0xEE000A10 | (vn << 16) | (rn_bits << 12) | (n << 7);
5470                Ok(vfp_to_thumb_bytes(instr))
5471            }
5472            ArmOp::MveDivF32 { qd, qn, qm } => {
5473                // Lane-wise: extract 4 S-regs, VDIV, insert back
5474                self.encode_thumb_mve_lane_wise_f32_binop(qd, qn, qm, 0xEE800A00)
5475            }
5476            ArmOp::MveSqrtF32 { qd, qm } => {
5477                // Lane-wise: extract 4 S-regs, VSQRT, insert back
5478                self.encode_thumb_mve_lane_wise_f32_sqrt(qd, qm)
5479            }
5480
5481            // Catch-all for any remaining ops
5482            _ => {
5483                let instr: u16 = 0xBF00; // NOP
5484                Ok(instr.to_le_bytes().to_vec())
5485            }
5486        }
5487    }
5488
5489    // === Thumb-2 VFP multi-instruction helpers ===
5490
5491    /// Encode F32 comparison as Thumb-2: VCMP.F32 + VMRS + MOVS rd,#0 + IT + MOV rd,#1
5492    fn encode_thumb_f32_compare(
5493        &self,
5494        rd: &Reg,
5495        sn: &VfpReg,
5496        sm: &VfpReg,
5497        cond_code: u32,
5498    ) -> Result<Vec<u8>> {
5499        let mut bytes = Vec::new();
5500        let rd_bits = reg_to_bits(rd);
5501
5502        // VCMP.F32 Sn, Sm
5503        let sn_num = vfp_sreg_to_num(sn)?;
5504        let sm_num = vfp_sreg_to_num(sm)?;
5505        let (vd, d) = encode_sreg(sn_num);
5506        let (vm, m) = encode_sreg(sm_num);
5507        let vcmp = 0xEEB40A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5508        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5509
5510        // VMRS APSR_nzcv, FPSCR: 0xEEF1FA10
5511        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5512
5513        // MOVS Rd, #0 (16-bit): 0010 0 Rd(3) 0000 0000
5514        if rd_bits < 8 {
5515            let movs_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
5516            bytes.extend_from_slice(&movs_zero.to_le_bytes());
5517        } else {
5518            // MOV.W Rd, #0 (32-bit Thumb-2)
5519            let hw1: u16 = 0xF04F;
5520            let hw2: u16 = (rd_bits as u16) << 8;
5521            bytes.extend_from_slice(&hw1.to_le_bytes());
5522            bytes.extend_from_slice(&hw2.to_le_bytes());
5523        }
5524
5525        // IT<cond> — If-Then for conditional MOV
5526        // IT encoding: 1011 1111 cond(4) mask(4)
5527        // mask = 0x8 for single "then" (IT)
5528        let it: u16 = 0xBF00 | ((cond_code as u16) << 4) | 0x8;
5529        bytes.extend_from_slice(&it.to_le_bytes());
5530
5531        // MOV Rd, #1 (16-bit, conditional due to IT): 0010 0 Rd(3) 0000 0001
5532        if rd_bits < 8 {
5533            let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
5534            bytes.extend_from_slice(&mov_one.to_le_bytes());
5535        } else {
5536            // MOV.W Rd, #1 (32-bit)
5537            let hw1: u16 = 0xF04F;
5538            let hw2: u16 = ((rd_bits as u16) << 8) | 0x01;
5539            bytes.extend_from_slice(&hw1.to_le_bytes());
5540            bytes.extend_from_slice(&hw2.to_le_bytes());
5541        }
5542
5543        Ok(bytes)
5544    }
5545
5546    /// Encode F32 constant load as Thumb-2: MOVW + MOVT + VMOV
5547    fn encode_thumb_f32_const(&self, sd: &VfpReg, value: f32) -> Result<Vec<u8>> {
5548        let mut bytes = Vec::new();
5549        let bits = value.to_bits();
5550        let rt: u32 = 12; // R12/IP as temp
5551
5552        // MOVW R12, #lo16
5553        // Thumb-2 MOVW: 11110 i 10 0100 imm4 | 0 imm3 Rd imm8
5554        let lo16 = bits & 0xFFFF;
5555        let imm4 = (lo16 >> 12) & 0xF;
5556        let i_bit = (lo16 >> 11) & 1;
5557        let imm3 = (lo16 >> 8) & 0x7;
5558        let imm8 = lo16 & 0xFF;
5559        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
5560        let hw2: u16 = ((imm3 << 12) | (rt << 8) | imm8) as u16;
5561        bytes.extend_from_slice(&hw1.to_le_bytes());
5562        bytes.extend_from_slice(&hw2.to_le_bytes());
5563
5564        // MOVT R12, #hi16
5565        let hi16 = (bits >> 16) & 0xFFFF;
5566        let imm4 = (hi16 >> 12) & 0xF;
5567        let i_bit = (hi16 >> 11) & 1;
5568        let imm3 = (hi16 >> 8) & 0x7;
5569        let imm8 = hi16 & 0xFF;
5570        let hw1: u16 = (0xF2C0 | (i_bit << 10) | imm4) as u16;
5571        let hw2: u16 = ((imm3 << 12) | (rt << 8) | imm8) as u16;
5572        bytes.extend_from_slice(&hw1.to_le_bytes());
5573        bytes.extend_from_slice(&hw2.to_le_bytes());
5574
5575        // VMOV Sd, R12
5576        let vmov = encode_vmov_core_sreg(true, sd, &Reg::R12)?;
5577        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5578
5579        Ok(bytes)
5580    }
5581
5582    /// Encode VMOV + VCVT.F32.xS32 as Thumb-2
5583    fn encode_thumb_f32_convert_i32(&self, sd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
5584        let mut bytes = Vec::new();
5585
5586        // VMOV Sd, Rm
5587        let vmov = encode_vmov_core_sreg(true, sd, rm)?;
5588        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5589
5590        // VCVT.F32.S32/U32 Sd, Sd
5591        let sd_num = vfp_sreg_to_num(sd)?;
5592        let (vd, d) = encode_sreg(sd_num);
5593        let (vm, m) = encode_sreg(sd_num);
5594        let base = if signed { 0xEEB80A40 } else { 0xEEB80AC0 };
5595        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
5596        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
5597
5598        Ok(bytes)
5599    }
5600
5601    /// Encode F32 rounding pseudo-op as Thumb-2 via VCVT to integer and back
5602    /// Encode F32 rounding as Thumb-2.
5603    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
5604    ///
5605    /// For trunc: uses VCVTR.S32.F32 (always truncates).
5606    /// For ceil/floor/nearest: sets FPSCR rounding mode, uses VCVT.S32.F32 (non-R variant),
5607    /// then restores FPSCR.
5608    fn encode_thumb_f32_rounding(&self, sd: &VfpReg, sm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
5609        let mut bytes = Vec::new();
5610        let sm_num = vfp_sreg_to_num(sm)?;
5611        let sd_num = vfp_sreg_to_num(sd)?;
5612        let (vd_s, d_s) = encode_sreg(sd_num);
5613        let (vm_s, m_s) = encode_sreg(sm_num);
5614
5615        if mode == 0b11 {
5616            // Trunc (toward zero): VCVTR.S32.F32 — bit[7]=1, always truncates
5617            let vcvt_to_int = 0xEEBD0AC0 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
5618            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5619        } else {
5620            // ceil/floor/nearest: manipulate FPSCR rounding mode
5621            let rt: u32 = 12; // R12/IP as temp
5622
5623            // VMRS R12, FPSCR
5624            let vmrs = 0xEEF10A10 | (rt << 12);
5625            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5626
5627            // BIC.W R12, R12, #(3 << 22) — clear RMode bits [23:22]
5628            // Thumb-2 modified immediate for 3<<22 = 0x00C00000:
5629            // BIC.W encoding: 11110 i 0 0001 S Rn | 0 imm3 Rd imm8
5630            // 0x00C00000 = 0x03 shifted left by 22 => Thumb mod-imm: i=0, imm3=0b101, imm8=0x03
5631            let bic_hw1: u16 = 0xF020 | ((rt as u16) & 0xF); // BIC, Rn=R12
5632            let bic_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | 0x03;
5633            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5634            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5635
5636            // ORR.W R12, R12, #(mode << 22)
5637            if mode != 0 {
5638                let orr_hw1: u16 = 0xF040 | ((rt as u16) & 0xF); // ORR, Rn=R12
5639                let orr_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | (mode as u16);
5640                bytes.extend_from_slice(&orr_hw1.to_le_bytes());
5641                bytes.extend_from_slice(&orr_hw2.to_le_bytes());
5642            }
5643
5644            // VMSR FPSCR, R12
5645            let vmsr = 0xEEE10A10 | (rt << 12);
5646            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5647
5648            // VCVT.S32.F32 Sd, Sm — non-R variant (bit[7]=0), uses FPSCR rmode
5649            let vcvt_to_int = 0xEEBD0A40 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
5650            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5651
5652            // Restore FPSCR: clear rmode bits back to nearest (default)
5653            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5654            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5655            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5656            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5657        }
5658
5659        // VCVT.F32.S32 Sd, Sd (convert integer result back to float)
5660        let (vd2, d2) = encode_sreg(sd_num);
5661        let vcvt_to_float = 0xEEB80A40 | (d2 << 22) | (vd2 << 12) | (d_s << 5) | vd_s;
5662        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_float));
5663
5664        Ok(bytes)
5665    }
5666
5667    /// Encode F32 min/max as Thumb-2: VMOV + VCMP + VMRS + IT + VMOV
5668    fn encode_thumb_f32_minmax(
5669        &self,
5670        sd: &VfpReg,
5671        sn: &VfpReg,
5672        sm: &VfpReg,
5673        is_min: bool,
5674    ) -> Result<Vec<u8>> {
5675        let mut bytes = Vec::new();
5676        let sn_num = vfp_sreg_to_num(sn)?;
5677        let sm_num = vfp_sreg_to_num(sm)?;
5678        let sd_num = vfp_sreg_to_num(sd)?;
5679
5680        // VMOV.F32 Sd, Sn
5681        let (vd, d) = encode_sreg(sd_num);
5682        let (vn, n) = encode_sreg(sn_num);
5683        let vmov_sn = 0xEEB00A40 | (d << 22) | (vd << 12) | (n << 5) | vn;
5684        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_sn));
5685
5686        // VCMP.F32 Sn, Sm
5687        let (vm, m) = encode_sreg(sm_num);
5688        let vcmp = 0xEEB40A40 | (n << 22) | (vn << 12) | (m << 5) | vm;
5689        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5690
5691        // VMRS APSR_nzcv, FPSCR
5692        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5693
5694        // IT GT (for min) or IT MI (for max)
5695        let cond: u16 = if is_min { 0xC } else { 0x4 };
5696        let it: u16 = 0xBF00 | (cond << 4) | 0x8;
5697        bytes.extend_from_slice(&it.to_le_bytes());
5698
5699        // VMOV{cond}.F32 Sd, Sm — conditional VMOV in IT block
5700        let vmov_sm = 0xEEB00A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5701        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_sm));
5702
5703        Ok(bytes)
5704    }
5705
5706    /// Encode F32 copysign as Thumb-2
5707    fn encode_thumb_f32_copysign(&self, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
5708        let mut bytes = Vec::new();
5709
5710        // VMOV R12, Sm (get sign source bits)
5711        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5712            false,
5713            sm,
5714            &Reg::R12,
5715        )?));
5716
5717        // VMOV R0, Sn (get magnitude source bits)
5718        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5719            false,
5720            sn,
5721            &Reg::R0,
5722        )?));
5723
5724        // AND.W R12, R12, #0x80000000
5725        // Thumb-2 modified immediate: 0x80000000 = constant 0x80 with rotation
5726        // Using T1 encoding: 11110 i 0 0000 S Rn | 0 imm3 Rd imm8
5727        // 0x80000000: i=0, imm3=0b001, imm8=0x00 (rotation=4, value=0x80)
5728        // Actually encoding #0x80000000 as modified constant:
5729        // bit pattern 1 followed by 31 zeros: enc = 0b0100_00000000 = 0x0100? No.
5730        // ARM modified immediate: abcdefgh rotated. 0x80000000 = 0x80 ROR 2 = enc 0x0102
5731        // Actually: value = abcdefgh ROR (2*rot). 0x80 = 10000000, ROR 2 gives 0x20000000.
5732        // For 0x80000000: 0x02 ROR 2 = 0x80000000. So imm12 = (1<<8) | 0x02 = 0x102
5733        let hw1: u16 = 0xF000 | 12; // AND.W R12, R12, #modified_const (i=0, Rn=R12)
5734        let hw2: u16 = (0x1 << 12) | (12 << 8) | 0x02; // imm3=1, Rd=R12, imm8=0x02
5735        bytes.extend_from_slice(&hw1.to_le_bytes());
5736        bytes.extend_from_slice(&hw2.to_le_bytes());
5737
5738        // BIC.W R0, R0, #0x80000000 (R0 = register 0, fields are zero)
5739        let hw1: u16 = 0xF020; // BIC.W R0, R0, #modified_const (i=0, Rn=R0)
5740        let hw2: u16 = (0x1 << 12) | 0x02; // imm3=1, Rd=R0, imm8=0x02
5741        bytes.extend_from_slice(&hw1.to_le_bytes());
5742        bytes.extend_from_slice(&hw2.to_le_bytes());
5743
5744        // ORR.W R0, R0, R12 (R0 = register 0)
5745        let hw1: u16 = 0xEA40; // ORR.W R0, R0, R12 (Rn=R0)
5746        let hw2: u16 = 12; // Rd=R0, Rm=R12
5747        bytes.extend_from_slice(&hw1.to_le_bytes());
5748        bytes.extend_from_slice(&hw2.to_le_bytes());
5749
5750        // VMOV Sd, R0
5751        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5752            true,
5753            sd,
5754            &Reg::R0,
5755        )?));
5756
5757        Ok(bytes)
5758    }
5759
5760    /// Encode F64 comparison as Thumb-2: VCMP.F64 + VMRS + MOV #0 + IT + MOV #1
5761    fn encode_thumb_f64_compare(
5762        &self,
5763        rd: &Reg,
5764        dn: &VfpReg,
5765        dm: &VfpReg,
5766        cond_code: u32,
5767    ) -> Result<Vec<u8>> {
5768        let mut bytes = Vec::new();
5769        let rd_bits = reg_to_bits(rd);
5770
5771        // VCMP.F64 Dn, Dm
5772        let dn_num = vfp_dreg_to_num(dn)?;
5773        let dm_num = vfp_dreg_to_num(dm)?;
5774        let (vd, d) = encode_dreg(dn_num);
5775        let (vm, m) = encode_dreg(dm_num);
5776        let vcmp = 0xEEB40B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5777        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5778
5779        // VMRS APSR_nzcv, FPSCR
5780        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5781
5782        // MOVS Rd, #0
5783        if rd_bits < 8 {
5784            let movs_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
5785            bytes.extend_from_slice(&movs_zero.to_le_bytes());
5786        } else {
5787            let hw1: u16 = 0xF04F;
5788            let hw2: u16 = (rd_bits as u16) << 8;
5789            bytes.extend_from_slice(&hw1.to_le_bytes());
5790            bytes.extend_from_slice(&hw2.to_le_bytes());
5791        }
5792
5793        // IT<cond>
5794        let it: u16 = 0xBF00 | ((cond_code as u16) << 4) | 0x8;
5795        bytes.extend_from_slice(&it.to_le_bytes());
5796
5797        // MOV Rd, #1
5798        if rd_bits < 8 {
5799            let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
5800            bytes.extend_from_slice(&mov_one.to_le_bytes());
5801        } else {
5802            let hw1: u16 = 0xF04F;
5803            let hw2: u16 = ((rd_bits as u16) << 8) | 0x01;
5804            bytes.extend_from_slice(&hw1.to_le_bytes());
5805            bytes.extend_from_slice(&hw2.to_le_bytes());
5806        }
5807
5808        Ok(bytes)
5809    }
5810
5811    /// Encode F64 constant load as Thumb-2: MOVW+MOVT (lo32 into R0) + MOVW+MOVT (hi32 into R12) + VMOV Dd, R0, R12
5812    fn encode_thumb_f64_const(&self, dd: &VfpReg, value: f64) -> Result<Vec<u8>> {
5813        let mut bytes = Vec::new();
5814        let bits = value.to_bits();
5815        let lo32 = bits as u32;
5816        let hi32 = (bits >> 32) as u32;
5817
5818        // MOVW R0, #lo16(lo32)
5819        let lo16 = lo32 & 0xFFFF;
5820        bytes.extend_from_slice(&self.encode_thumb32_movw_raw(0, lo16)?);
5821
5822        // MOVT R0, #hi16(lo32)
5823        let hi16 = (lo32 >> 16) & 0xFFFF;
5824        bytes.extend_from_slice(&self.encode_thumb32_movt_raw(0, hi16)?);
5825
5826        // MOVW R12, #lo16(hi32)
5827        let lo16 = hi32 & 0xFFFF;
5828        bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, lo16)?);
5829
5830        // MOVT R12, #hi16(hi32)
5831        let hi16 = (hi32 >> 16) & 0xFFFF;
5832        bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, hi16)?);
5833
5834        // VMOV Dd, R0, R12
5835        let vmov = encode_vmov_core_dreg(true, dd, &Reg::R0, &Reg::R12)?;
5836        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5837
5838        Ok(bytes)
5839    }
5840
5841    /// Encode VMOV Sd, Rm + VCVT.F64.S32/U32 Dd, Sd as Thumb-2
5842    fn encode_thumb_f64_convert_i32(&self, dd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
5843        let mut bytes = Vec::new();
5844
5845        // VMOV S0, Rm
5846        let vmov = encode_vmov_core_sreg(true, &VfpReg::S0, rm)?;
5847        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5848
5849        // VCVT.F64.S32 Dd, S0 or VCVT.F64.U32 Dd, S0
5850        let dd_num = vfp_dreg_to_num(dd)?;
5851        let (vd, d) = encode_dreg(dd_num);
5852        let base = if signed { 0xEEB80B40 } else { 0xEEB80BC0 };
5853        let vcvt = base | (d << 22) | (vd << 12);
5854        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
5855
5856        Ok(bytes)
5857    }
5858
5859    /// Encode VCVT.F64.F32 Dd, Sm as Thumb-2
5860    fn encode_thumb_f64_promote_f32(&self, dd: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
5861        let dd_num = vfp_dreg_to_num(dd)?;
5862        let sm_num = vfp_sreg_to_num(sm)?;
5863        let (vd, d) = encode_dreg(dd_num);
5864        let (vm, m) = encode_sreg(sm_num);
5865
5866        let vcvt = 0xEEB70AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
5867        Ok(vfp_to_thumb_bytes(vcvt))
5868    }
5869
5870    /// Encode VCVT.S32/U32.F64 S0, Dm + VMOV Rd, S0 as Thumb-2
5871    fn encode_thumb_i32_trunc_f64(&self, rd: &Reg, dm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
5872        let mut bytes = Vec::new();
5873        let dm_num = vfp_dreg_to_num(dm)?;
5874        let (vm, m) = encode_dreg(dm_num);
5875
5876        // VCVT.S32.F64 S0, Dm or VCVT.U32.F64 S0, Dm
5877        let base = if signed { 0xEEBD0BC0 } else { 0xEEBC0BC0 };
5878        let vcvt = base | (m << 5) | vm;
5879        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
5880
5881        // VMOV Rd, S0
5882        let vmov = encode_vmov_core_sreg(false, &VfpReg::S0, rd)?;
5883        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5884
5885        Ok(bytes)
5886    }
5887
5888    /// Encode F64 rounding pseudo-op as Thumb-2 via VCVT to integer and back
5889    /// Encode F64 rounding as Thumb-2.
5890    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
5891    fn encode_thumb_f64_rounding(&self, dd: &VfpReg, dm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
5892        let mut bytes = Vec::new();
5893        let dm_num = vfp_dreg_to_num(dm)?;
5894        let dd_num = vfp_dreg_to_num(dd)?;
5895        let (vm, m) = encode_dreg(dm_num);
5896        let (vd, d) = encode_dreg(dd_num);
5897
5898        if mode == 0b11 {
5899            // Trunc: VCVTR.S32.F64 — bit[7]=1, always truncates
5900            let vcvt_to_int = 0xEEBD0BC0 | (m << 5) | vm;
5901            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5902        } else {
5903            let rt: u32 = 12;
5904
5905            // VMRS R12, FPSCR
5906            let vmrs = 0xEEF10A10 | (rt << 12);
5907            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5908
5909            // BIC.W R12, R12, #(3 << 22)
5910            let bic_hw1: u16 = 0xF020 | ((rt as u16) & 0xF);
5911            let bic_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | 0x03;
5912            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5913            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5914
5915            // ORR.W R12, R12, #(mode << 22)
5916            if mode != 0 {
5917                let orr_hw1: u16 = 0xF040 | ((rt as u16) & 0xF);
5918                let orr_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | (mode as u16);
5919                bytes.extend_from_slice(&orr_hw1.to_le_bytes());
5920                bytes.extend_from_slice(&orr_hw2.to_le_bytes());
5921            }
5922
5923            // VMSR FPSCR, R12
5924            let vmsr = 0xEEE10A10 | (rt << 12);
5925            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5926
5927            // VCVT.S32.F64 S0, Dm — non-R variant (bit[7]=0)
5928            let vcvt_to_int = 0xEEBD0B40 | (m << 5) | vm;
5929            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5930
5931            // Restore FPSCR
5932            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5933            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5934            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5935            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5936        }
5937
5938        // VCVT.F64.S32 Dd, S0
5939        let vcvt_to_float = 0xEEB80B40 | (d << 22) | (vd << 12);
5940        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_float));
5941
5942        Ok(bytes)
5943    }
5944
5945    /// Encode F64 min/max as Thumb-2
5946    fn encode_thumb_f64_minmax(
5947        &self,
5948        dd: &VfpReg,
5949        dn: &VfpReg,
5950        dm: &VfpReg,
5951        is_min: bool,
5952    ) -> Result<Vec<u8>> {
5953        let mut bytes = Vec::new();
5954        let dn_num = vfp_dreg_to_num(dn)?;
5955        let dm_num = vfp_dreg_to_num(dm)?;
5956        let dd_num = vfp_dreg_to_num(dd)?;
5957
5958        // VMOV.F64 Dd, Dn
5959        let (vd, d) = encode_dreg(dd_num);
5960        let (vn, n) = encode_dreg(dn_num);
5961        let vmov_dn = 0xEEB00B40 | (d << 22) | (vd << 12) | (n << 5) | vn;
5962        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_dn));
5963
5964        // VCMP.F64 Dn, Dm
5965        let (vm, m) = encode_dreg(dm_num);
5966        let vcmp = 0xEEB40B40 | (n << 22) | (vn << 12) | (m << 5) | vm;
5967        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5968
5969        // VMRS APSR_nzcv, FPSCR
5970        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5971
5972        // IT GT (for min) or IT MI (for max)
5973        let cond: u16 = if is_min { 0xC } else { 0x4 };
5974        let it: u16 = 0xBF00 | (cond << 4) | 0x8;
5975        bytes.extend_from_slice(&it.to_le_bytes());
5976
5977        // VMOV{cond}.F64 Dd, Dm
5978        let vmov_dm = 0xEEB00B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5979        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_dm));
5980
5981        Ok(bytes)
5982    }
5983
5984    /// Encode F64 copysign as Thumb-2
5985    fn encode_thumb_f64_copysign(&self, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<Vec<u8>> {
5986        let mut bytes = Vec::new();
5987
5988        // VMOV R0, R12, Dm (get sign source)
5989        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
5990            false,
5991            dm,
5992            &Reg::R0,
5993            &Reg::R12,
5994        )?));
5995
5996        // VMOV R1, R2, Dn (get magnitude source)
5997        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
5998            false,
5999            dn,
6000            &Reg::R1,
6001            &Reg::R2,
6002        )?));
6003
6004        // AND.W R12, R12, #0x80000000 (i=0, Rn=R12)
6005        let hw1: u16 = 0xF000 | 12;
6006        let hw2: u16 = (0x1 << 12) | (12 << 8) | 0x02;
6007        bytes.extend_from_slice(&hw1.to_le_bytes());
6008        bytes.extend_from_slice(&hw2.to_le_bytes());
6009
6010        // BIC.W R2, R2, #0x80000000 (i=0, Rn=R2)
6011        let hw1: u16 = 0xF020 | 2;
6012        let hw2: u16 = (0x1 << 12) | (2 << 8) | 0x02;
6013        bytes.extend_from_slice(&hw1.to_le_bytes());
6014        bytes.extend_from_slice(&hw2.to_le_bytes());
6015
6016        // ORR.W R2, R2, R12
6017        let hw1: u16 = 0xEA40 | 2;
6018        let hw2: u16 = (2 << 8) | 12;
6019        bytes.extend_from_slice(&hw1.to_le_bytes());
6020        bytes.extend_from_slice(&hw2.to_le_bytes());
6021
6022        // VMOV Dd, R1, R2
6023        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
6024            true,
6025            dd,
6026            &Reg::R1,
6027            &Reg::R2,
6028        )?));
6029
6030        Ok(bytes)
6031    }
6032
6033    /// Encode VCVT.S32/U32.F32 + VMOV as Thumb-2
6034    fn encode_thumb_i32_trunc_f32(&self, rd: &Reg, sm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
6035        let mut bytes = Vec::new();
6036
6037        let sm_num = vfp_sreg_to_num(sm)?;
6038        let (vd, d) = encode_sreg(sm_num);
6039        let (vm, m) = encode_sreg(sm_num);
6040        let base = if signed { 0xEEBD0AC0 } else { 0xEEBC0AC0 };
6041        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
6042        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
6043
6044        // VMOV Rd, Sm
6045        let vmov = encode_vmov_core_sreg(false, sm, rd)?;
6046        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
6047
6048        Ok(bytes)
6049    }
6050
6051    // === Thumb-2 32-bit encoding helpers ===
6052
6053    /// Encode Thumb-2 32-bit ADD with immediate
6054    fn encode_thumb32_add(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6055        let rd_bits = reg_to_bits(rd);
6056        let rn_bits = reg_to_bits(rn);
6057
6058        // The `i:imm3:imm8` field is split the same way for both forms.
6059        let i_bit = (imm >> 11) & 1;
6060        let imm3 = (imm >> 8) & 0x7;
6061        let imm8 = imm & 0xFF;
6062
6063        let hw1_base = if imm <= 0xFF {
6064            // ADD.W (T3): the field is a ThumbExpandImm modified immediate. For
6065            // imm <= 0xFF (i:imm3 = 0000) it is the zero-extended byte, which is
6066            // correct — keep this form so existing encodings stay bit-identical.
6067            0xF100
6068        } else if imm <= 0xFFF {
6069            // ADDW (T4): a PLAIN 12-bit immediate (0..4095) — no ThumbExpandImm.
6070            // This is what makes `add sp, sp, #frame` correct for frame sizes
6071            // >= 256, which ADD.W (T3) would silently mis-encode (e.g. #256 -> #0).
6072            0xF200
6073        } else {
6074            return Err(synth_core::Error::synthesis(
6075                "ADD immediate > 0xFFF (4095) requires a multi-instruction sequence (not supported)",
6076            ));
6077        };
6078
6079        let hw1: u16 = (hw1_base | (i_bit << 10) | rn_bits) as u16;
6080        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6081
6082        let mut bytes = hw1.to_le_bytes().to_vec();
6083        bytes.extend_from_slice(&hw2.to_le_bytes());
6084        Ok(bytes)
6085    }
6086
6087    /// Encode Thumb-2 32-bit SUB with immediate
6088    fn encode_thumb32_sub(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6089        let rd_bits = reg_to_bits(rd);
6090        let rn_bits = reg_to_bits(rn);
6091
6092        let i_bit = (imm >> 11) & 1;
6093        let imm3 = (imm >> 8) & 0x7;
6094        let imm8 = imm & 0xFF;
6095
6096        let hw1_base = if imm <= 0xFF {
6097            // SUB.W (T3) modified immediate — correct for the zero-extended byte
6098            // (imm <= 0xFF). Kept bit-identical for existing encodings.
6099            0xF1A0
6100        } else if imm <= 0xFFF {
6101            // SUBW (T4): plain 12-bit immediate (0..4095). Makes
6102            // `sub sp, sp, #frame` correct for frame sizes >= 256.
6103            0xF2A0
6104        } else {
6105            return Err(synth_core::Error::synthesis(
6106                "SUB immediate > 0xFFF (4095) requires a multi-instruction sequence (not supported)",
6107            ));
6108        };
6109
6110        let hw1: u16 = (hw1_base | (i_bit << 10) | rn_bits) as u16;
6111        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6112
6113        let mut bytes = hw1.to_le_bytes().to_vec();
6114        bytes.extend_from_slice(&hw2.to_le_bytes());
6115        Ok(bytes)
6116    }
6117
6118    /// Encode Thumb-2 32-bit ADDS with immediate (sets flags)
6119    fn encode_thumb32_adds(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6120        let rd_bits = reg_to_bits(rd);
6121        let rn_bits = reg_to_bits(rn);
6122
6123        // ADDS.W (flag-setting) has only the modified-immediate form — error on
6124        // an un-encodable value rather than silently add the wrong constant.
6125        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6126            synth_core::Error::synthesis(
6127                "ADDS immediate is not a valid ThumbExpandImm — materialize into a register",
6128            )
6129        })?;
6130        let i_bit = (field >> 11) & 1;
6131        let imm3 = (field >> 8) & 0x7;
6132        let imm8 = field & 0xFF;
6133
6134        // ADDS.W Rd, Rn, #imm (with S=1)
6135        // First halfword: 1111 0 i 0 1000 1 Rn = F110 | i<<10 | Rn
6136        let hw1: u16 = (0xF110 | (i_bit << 10) | rn_bits) as u16;
6137        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6138
6139        let mut bytes = hw1.to_le_bytes().to_vec();
6140        bytes.extend_from_slice(&hw2.to_le_bytes());
6141        Ok(bytes)
6142    }
6143
6144    /// Encode Thumb-2 32-bit SUBS with immediate (sets flags)
6145    fn encode_thumb32_subs(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6146        let rd_bits = reg_to_bits(rd);
6147        let rn_bits = reg_to_bits(rn);
6148
6149        // SUBS.W (flag-setting) has only the modified-immediate form — error on
6150        // an un-encodable value rather than silently subtract the wrong constant.
6151        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6152            synth_core::Error::synthesis(
6153                "SUBS immediate is not a valid ThumbExpandImm — materialize into a register",
6154            )
6155        })?;
6156        let i_bit = (field >> 11) & 1;
6157        let imm3 = (field >> 8) & 0x7;
6158        let imm8 = field & 0xFF;
6159
6160        // SUBS.W Rd, Rn, #imm (with S=1)
6161        // First halfword: 1111 0 i 0 1101 1 Rn = F1B0 | i<<10 | Rn
6162        let hw1: u16 = (0xF1B0 | (i_bit << 10) | rn_bits) as u16;
6163        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6164
6165        let mut bytes = hw1.to_le_bytes().to_vec();
6166        bytes.extend_from_slice(&hw2.to_le_bytes());
6167        Ok(bytes)
6168    }
6169
6170    /// Encode Thumb-2 32-bit MOVW (16-bit immediate)
6171    ///
6172    /// # Contract (Verus-style)
6173    /// ```text
6174    /// requires rd <= R14
6175    /// ensures result.len() == 4
6176    /// ensures (imm & 0xFFFF) can be reconstructed from the encoding
6177    /// ```
6178    fn encode_thumb32_movw(&self, rd: &Reg, imm: u32) -> Result<Vec<u8>> {
6179        let rd_bits = reg_to_bits(rd);
6180        reg_bits_checked(rd_bits)?;
6181        let imm16 = imm & 0xFFFF;
6182
6183        // MOVW Rd, #imm16
6184        // 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
6185        let imm4 = (imm16 >> 12) & 0xF;
6186        let i_bit = (imm16 >> 11) & 1;
6187        let imm3 = (imm16 >> 8) & 0x7;
6188        let imm8 = imm16 & 0xFF;
6189
6190        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
6191        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6192
6193        let mut bytes = hw1.to_le_bytes().to_vec();
6194        bytes.extend_from_slice(&hw2.to_le_bytes());
6195        encoding_contracts::verify_thumb32(&bytes);
6196        Ok(bytes)
6197    }
6198
6199    /// Encode Thumb-2 32-bit shift with immediate
6200    ///
6201    /// # Contract (Verus-style)
6202    /// ```text
6203    /// requires rd <= R14, rm <= R14
6204    /// ensures result.len() == 4
6205    /// ```
6206    fn encode_thumb32_shift(
6207        &self,
6208        rd: &Reg,
6209        rm: &Reg,
6210        shift: u32,
6211        shift_type: u8,
6212    ) -> Result<Vec<u8>> {
6213        let rd_bits = reg_to_bits(rd);
6214        let rm_bits = reg_to_bits(rm);
6215        reg_bits_checked(rd_bits)?;
6216        reg_bits_checked(rm_bits)?;
6217        let imm5 = shift & 0x1F;
6218        let imm2 = imm5 & 0x3;
6219        let imm3 = (imm5 >> 2) & 0x7;
6220
6221        // MOV.W Rd, Rm, <shift> #imm
6222        // EA4F 0 imm3 Rd imm2 type Rm
6223        let hw1: u16 = 0xEA4F;
6224        let hw2: u16 =
6225            ((imm3 << 12) | (rd_bits << 8) | (imm2 << 6) | ((shift_type as u32) << 4) | rm_bits)
6226                as u16;
6227
6228        let mut bytes = hw1.to_le_bytes().to_vec();
6229        bytes.extend_from_slice(&hw2.to_le_bytes());
6230        Ok(bytes)
6231    }
6232
6233    /// Encode Thumb-2 32-bit shift by register
6234    /// Encoding: 11111010 0xx0 Rn | 1111 Rd 0000 Rm
6235    /// shift_type: 00=LSL, 01=LSR, 10=ASR, 11=ROR
6236    fn encode_thumb32_shift_reg(
6237        &self,
6238        rd: &Reg,
6239        rn: &Reg,
6240        rm: &Reg,
6241        shift_type: u8,
6242    ) -> Result<Vec<u8>> {
6243        let rd_bits = reg_to_bits(rd);
6244        let rn_bits = reg_to_bits(rn);
6245        let rm_bits = reg_to_bits(rm);
6246
6247        // hw1: 1111 1010 0xx0 Rn
6248        let hw1: u16 = (0xFA00 | ((shift_type as u32) << 5) | rn_bits) as u16;
6249        // hw2: 1111 Rd 0000 Rm
6250        let hw2: u16 = (0xF000 | (rd_bits << 8) | rm_bits) as u16;
6251
6252        let mut bytes = hw1.to_le_bytes().to_vec();
6253        bytes.extend_from_slice(&hw2.to_le_bytes());
6254        Ok(bytes)
6255    }
6256
6257    /// Encode Thumb-2 32-bit CMP with immediate
6258    fn encode_thumb32_cmp_imm(&self, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6259        let rn_bits = reg_to_bits(rn);
6260
6261        // CMP.W has only the modified-immediate form (no plain-imm12 like ADDW),
6262        // so an un-encodable immediate MUST be materialized into a register by
6263        // the selector. Error rather than silently compare the wrong constant.
6264        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6265            synth_core::Error::synthesis(
6266                "CMP immediate is not a valid ThumbExpandImm — materialize into a register",
6267            )
6268        })?;
6269        let i_bit = (field >> 11) & 1;
6270        let imm3 = (field >> 8) & 0x7;
6271        let imm8 = field & 0xFF;
6272
6273        // CMP.W Rn, #imm
6274        let hw1: u16 = (0xF1B0 | (i_bit << 10) | rn_bits) as u16;
6275        let hw2: u16 = ((imm3 << 12) | 0x0F00 | imm8) as u16;
6276
6277        let mut bytes = hw1.to_le_bytes().to_vec();
6278        bytes.extend_from_slice(&hw2.to_le_bytes());
6279        Ok(bytes)
6280    }
6281
6282    /// Encode Thumb-2 32-bit LDR
6283    fn encode_thumb32_ldr(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6284        let rd_bits = reg_to_bits(rd);
6285        let base_bits = reg_to_bits(base);
6286
6287        // LDR.W Rd, [Rn, #imm12]
6288        check_ldst_imm12(offset)?;
6289        let hw1: u16 = (0xF8D0 | base_bits) as u16;
6290        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6291
6292        let mut bytes = hw1.to_le_bytes().to_vec();
6293        bytes.extend_from_slice(&hw2.to_le_bytes());
6294        Ok(bytes)
6295    }
6296
6297    /// Encode Thumb-2 32-bit STR
6298    fn encode_thumb32_str(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6299        let rd_bits = reg_to_bits(rd);
6300        let base_bits = reg_to_bits(base);
6301
6302        // STR.W Rd, [Rn, #imm12]
6303        check_ldst_imm12(offset)?;
6304        let hw1: u16 = (0xF8C0 | base_bits) as u16;
6305        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6306
6307        let mut bytes = hw1.to_le_bytes().to_vec();
6308        bytes.extend_from_slice(&hw2.to_le_bytes());
6309        Ok(bytes)
6310    }
6311
6312    /// Encode Thumb-2 32-bit LDR with register offset: LDR.W Rd, [Rn, Rm]
6313    fn encode_thumb32_ldr_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6314        let rd_bits = reg_to_bits(rd);
6315        let base_bits = reg_to_bits(base);
6316        let rm_bits = reg_to_bits(offset_reg);
6317
6318        // LDR.W Rd, [Rn, Rm, LSL #0]
6319        // Encoding: 1111 1000 0101 Rn | Rt 0000 00 imm2 Rm
6320        // imm2 = 00 for no shift (LSL #0)
6321        let hw1: u16 = (0xF850 | base_bits) as u16;
6322        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6323
6324        let mut bytes = hw1.to_le_bytes().to_vec();
6325        bytes.extend_from_slice(&hw2.to_le_bytes());
6326        Ok(bytes)
6327    }
6328
6329    /// Encode Thumb-2 32-bit STR with register offset: STR.W Rd, [Rn, Rm]
6330    fn encode_thumb32_str_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6331        let rd_bits = reg_to_bits(rd);
6332        let base_bits = reg_to_bits(base);
6333        let rm_bits = reg_to_bits(offset_reg);
6334
6335        // STR.W Rd, [Rn, Rm, LSL #0]
6336        // Encoding: 1111 1000 0100 Rn | Rt 0000 00 imm2 Rm
6337        // imm2 = 00 for no shift (LSL #0)
6338        let hw1: u16 = (0xF840 | base_bits) as u16;
6339        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6340
6341        let mut bytes = hw1.to_le_bytes().to_vec();
6342        bytes.extend_from_slice(&hw2.to_le_bytes());
6343        Ok(bytes)
6344    }
6345
6346    // === Sub-word load/store Thumb-2 encoding helpers ===
6347
6348    /// Encode Thumb-2 32-bit LDRB with immediate: LDRB.W Rd, [Rn, #imm12]
6349    fn encode_thumb32_ldrb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6350        let rd_bits = reg_to_bits(rd);
6351        let base_bits = reg_to_bits(base);
6352        // LDRB.W Rd, [Rn, #imm12]: 1111 1000 1001 Rn | Rt imm12
6353        check_ldst_imm12(offset)?;
6354        let hw1: u16 = (0xF890 | base_bits) as u16;
6355        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6356        let mut bytes = hw1.to_le_bytes().to_vec();
6357        bytes.extend_from_slice(&hw2.to_le_bytes());
6358        Ok(bytes)
6359    }
6360
6361    /// Encode Thumb-2 32-bit LDRB with register: LDRB.W Rd, [Rn, Rm]
6362    fn encode_thumb32_ldrb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6363        let rd_bits = reg_to_bits(rd);
6364        let base_bits = reg_to_bits(base);
6365        let rm_bits = reg_to_bits(offset_reg);
6366        // LDRB.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0001 Rn | Rt 0000 00 imm2 Rm
6367        let hw1: u16 = (0xF810 | base_bits) as u16;
6368        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6369        let mut bytes = hw1.to_le_bytes().to_vec();
6370        bytes.extend_from_slice(&hw2.to_le_bytes());
6371        Ok(bytes)
6372    }
6373
6374    /// Encode Thumb-2 32-bit LDRSB with immediate: LDRSB.W Rd, [Rn, #imm12]
6375    fn encode_thumb32_ldrsb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6376        let rd_bits = reg_to_bits(rd);
6377        let base_bits = reg_to_bits(base);
6378        // LDRSB.W Rd, [Rn, #imm12]: 1111 1001 1001 Rn | Rt imm12
6379        check_ldst_imm12(offset)?;
6380        let hw1: u16 = (0xF990 | base_bits) as u16;
6381        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6382        let mut bytes = hw1.to_le_bytes().to_vec();
6383        bytes.extend_from_slice(&hw2.to_le_bytes());
6384        Ok(bytes)
6385    }
6386
6387    /// Encode Thumb-2 32-bit LDRSB with register: LDRSB.W Rd, [Rn, Rm]
6388    fn encode_thumb32_ldrsb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6389        let rd_bits = reg_to_bits(rd);
6390        let base_bits = reg_to_bits(base);
6391        let rm_bits = reg_to_bits(offset_reg);
6392        // LDRSB.W Rd, [Rn, Rm, LSL #0]: 1111 1001 0001 Rn | Rt 0000 00 imm2 Rm
6393        let hw1: u16 = (0xF910 | base_bits) as u16;
6394        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6395        let mut bytes = hw1.to_le_bytes().to_vec();
6396        bytes.extend_from_slice(&hw2.to_le_bytes());
6397        Ok(bytes)
6398    }
6399
6400    /// Encode Thumb-2 32-bit LDRH with immediate: LDRH.W Rd, [Rn, #imm12]
6401    fn encode_thumb32_ldrh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6402        let rd_bits = reg_to_bits(rd);
6403        let base_bits = reg_to_bits(base);
6404        // LDRH.W Rd, [Rn, #imm12]: 1111 1000 1011 Rn | Rt imm12
6405        check_ldst_imm12(offset)?;
6406        let hw1: u16 = (0xF8B0 | base_bits) as u16;
6407        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6408        let mut bytes = hw1.to_le_bytes().to_vec();
6409        bytes.extend_from_slice(&hw2.to_le_bytes());
6410        Ok(bytes)
6411    }
6412
6413    /// Encode Thumb-2 32-bit LDRH with register: LDRH.W Rd, [Rn, Rm]
6414    fn encode_thumb32_ldrh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6415        let rd_bits = reg_to_bits(rd);
6416        let base_bits = reg_to_bits(base);
6417        let rm_bits = reg_to_bits(offset_reg);
6418        // LDRH.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0011 Rn | Rt 0000 00 imm2 Rm
6419        let hw1: u16 = (0xF830 | base_bits) as u16;
6420        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6421        let mut bytes = hw1.to_le_bytes().to_vec();
6422        bytes.extend_from_slice(&hw2.to_le_bytes());
6423        Ok(bytes)
6424    }
6425
6426    /// Encode Thumb-2 32-bit LDRSH with immediate: LDRSH.W Rd, [Rn, #imm12]
6427    fn encode_thumb32_ldrsh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6428        let rd_bits = reg_to_bits(rd);
6429        let base_bits = reg_to_bits(base);
6430        // LDRSH.W Rd, [Rn, #imm12]: 1111 1001 1011 Rn | Rt imm12
6431        check_ldst_imm12(offset)?;
6432        let hw1: u16 = (0xF9B0 | base_bits) as u16;
6433        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6434        let mut bytes = hw1.to_le_bytes().to_vec();
6435        bytes.extend_from_slice(&hw2.to_le_bytes());
6436        Ok(bytes)
6437    }
6438
6439    /// Encode Thumb-2 32-bit LDRSH with register: LDRSH.W Rd, [Rn, Rm]
6440    fn encode_thumb32_ldrsh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6441        let rd_bits = reg_to_bits(rd);
6442        let base_bits = reg_to_bits(base);
6443        let rm_bits = reg_to_bits(offset_reg);
6444        // LDRSH.W Rd, [Rn, Rm, LSL #0]: 1111 1001 0011 Rn | Rt 0000 00 imm2 Rm
6445        let hw1: u16 = (0xF930 | base_bits) as u16;
6446        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6447        let mut bytes = hw1.to_le_bytes().to_vec();
6448        bytes.extend_from_slice(&hw2.to_le_bytes());
6449        Ok(bytes)
6450    }
6451
6452    /// Encode Thumb-2 32-bit STRB with immediate: STRB.W Rd, [Rn, #imm12]
6453    fn encode_thumb32_strb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6454        let rd_bits = reg_to_bits(rd);
6455        let base_bits = reg_to_bits(base);
6456        // STRB.W Rd, [Rn, #imm12]: 1111 1000 1000 Rn | Rt imm12
6457        check_ldst_imm12(offset)?;
6458        let hw1: u16 = (0xF880 | base_bits) as u16;
6459        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6460        let mut bytes = hw1.to_le_bytes().to_vec();
6461        bytes.extend_from_slice(&hw2.to_le_bytes());
6462        Ok(bytes)
6463    }
6464
6465    /// Encode Thumb-2 32-bit STRB with register: STRB.W Rd, [Rn, Rm]
6466    fn encode_thumb32_strb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6467        let rd_bits = reg_to_bits(rd);
6468        let base_bits = reg_to_bits(base);
6469        let rm_bits = reg_to_bits(offset_reg);
6470        // STRB.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0000 Rn | Rt 0000 00 imm2 Rm
6471        let hw1: u16 = (0xF800 | base_bits) as u16;
6472        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6473        let mut bytes = hw1.to_le_bytes().to_vec();
6474        bytes.extend_from_slice(&hw2.to_le_bytes());
6475        Ok(bytes)
6476    }
6477
6478    /// Encode Thumb-2 32-bit STRH with immediate: STRH.W Rd, [Rn, #imm12]
6479    fn encode_thumb32_strh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6480        let rd_bits = reg_to_bits(rd);
6481        let base_bits = reg_to_bits(base);
6482        // STRH.W Rd, [Rn, #imm12]: 1111 1000 1010 Rn | Rt imm12
6483        check_ldst_imm12(offset)?;
6484        let hw1: u16 = (0xF8A0 | base_bits) as u16;
6485        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6486        let mut bytes = hw1.to_le_bytes().to_vec();
6487        bytes.extend_from_slice(&hw2.to_le_bytes());
6488        Ok(bytes)
6489    }
6490
6491    /// Encode Thumb-2 32-bit STRH with register: STRH.W Rd, [Rn, Rm]
6492    fn encode_thumb32_strh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6493        let rd_bits = reg_to_bits(rd);
6494        let base_bits = reg_to_bits(base);
6495        let rm_bits = reg_to_bits(offset_reg);
6496        // STRH.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0010 Rn | Rt 0000 00 imm2 Rm
6497        let hw1: u16 = (0xF820 | base_bits) as u16;
6498        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6499        let mut bytes = hw1.to_le_bytes().to_vec();
6500        bytes.extend_from_slice(&hw2.to_le_bytes());
6501        Ok(bytes)
6502    }
6503
6504    /// Encode Thumb-2 32-bit ADD with immediate: ADD.W Rd, Rn, #imm
6505    fn encode_thumb32_add_imm(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6506        let rd_bits = reg_to_bits(rd);
6507        let rn_bits = reg_to_bits(rn);
6508
6509        // For small immediates, use ADD.W Rd, Rn, #imm12
6510        // Encoding: 1111 0 i 0 1 0 0 0 S Rn | 0 imm3 Rd imm8
6511        // S = 0 (don't update flags)
6512        // The 12-bit immediate is encoded as: i:imm3:imm8
6513        // For simplicity, we only support imm <= 0xFFF (direct encoding)
6514        if imm <= 0xFFF {
6515            let i_bit = (imm >> 11) & 1;
6516            let imm3 = (imm >> 8) & 0x7;
6517            let imm8 = imm & 0xFF;
6518
6519            let hw1: u16 = (0xF100 | (i_bit << 10) | rn_bits) as u16;
6520            let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6521
6522            let mut bytes = hw1.to_le_bytes().to_vec();
6523            bytes.extend_from_slice(&hw2.to_le_bytes());
6524            Ok(bytes)
6525        } else {
6526            // For larger immediates, would need MOVW/MOVT + ADD
6527            // For now, return error
6528            Err(synth_core::Error::synthesis(
6529                "ADD immediate too large for single instruction",
6530            ))
6531        }
6532    }
6533
6534    // === Raw encoding helpers for POPCNT (take register numbers directly) ===
6535
6536    /// Encode Thumb-2 32-bit MOVW (16-bit immediate) - raw version
6537    ///
6538    /// # Contract (Verus-style)
6539    /// ```text
6540    /// requires rd <= 14, imm16 <= 0xFFFF
6541    /// ensures result.len() == 4
6542    /// ```
6543    fn encode_thumb32_movw_raw(&self, rd: u32, imm16: u32) -> Result<Vec<u8>> {
6544        reg_bits_checked(rd)?;
6545        encoding_contracts::verify_imm16(imm16);
6546        // MOVW Rd, #imm16
6547        // 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
6548        let imm16 = imm16 & 0xFFFF;
6549        let imm4 = (imm16 >> 12) & 0xF;
6550        let i_bit = (imm16 >> 11) & 1;
6551        let imm3 = (imm16 >> 8) & 0x7;
6552        let imm8 = imm16 & 0xFF;
6553
6554        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
6555        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6556
6557        let mut bytes = hw1.to_le_bytes().to_vec();
6558        bytes.extend_from_slice(&hw2.to_le_bytes());
6559        encoding_contracts::verify_thumb32(&bytes);
6560        Ok(bytes)
6561    }
6562
6563    /// Encode Thumb-2 32-bit MOVT (move top 16 bits) - raw version
6564    ///
6565    /// # Contract (Verus-style)
6566    /// ```text
6567    /// requires rd <= 14, imm16 <= 0xFFFF
6568    /// ensures result.len() == 4
6569    /// ```
6570    fn encode_thumb32_movt_raw(&self, rd: u32, imm16: u32) -> Result<Vec<u8>> {
6571        reg_bits_checked(rd)?;
6572        encoding_contracts::verify_imm16(imm16);
6573        // MOVT Rd, #imm16
6574        // 1111 0 i 10 1 1 0 0 imm4 | 0 imm3 Rd imm8
6575        let imm16 = imm16 & 0xFFFF;
6576        let imm4 = (imm16 >> 12) & 0xF;
6577        let i_bit = (imm16 >> 11) & 1;
6578        let imm3 = (imm16 >> 8) & 0x7;
6579        let imm8 = imm16 & 0xFF;
6580
6581        let hw1: u16 = (0xF2C0 | (i_bit << 10) | imm4) as u16;
6582        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6583
6584        let mut bytes = hw1.to_le_bytes().to_vec();
6585        bytes.extend_from_slice(&hw2.to_le_bytes());
6586        encoding_contracts::verify_thumb32(&bytes);
6587        Ok(bytes)
6588    }
6589
6590    /// Encode Thumb-2 32-bit LSR (logical shift right) with immediate - raw version
6591    fn encode_thumb32_lsr_raw(&self, rd: u32, rm: u32, shift: u32) -> Result<Vec<u8>> {
6592        // MOV.W Rd, Rm, LSR #imm
6593        // EA4F 0 imm3 Rd imm2 01 Rm
6594        let imm5 = shift & 0x1F;
6595        let imm2 = imm5 & 0x3;
6596        let imm3 = (imm5 >> 2) & 0x7;
6597
6598        let hw1: u16 = 0xEA4F;
6599        let hw2: u16 = ((imm3 << 12) | (rd << 8) | (imm2 << 6) | (0b01 << 4) | rm) as u16;
6600
6601        let mut bytes = hw1.to_le_bytes().to_vec();
6602        bytes.extend_from_slice(&hw2.to_le_bytes());
6603        Ok(bytes)
6604    }
6605
6606    /// Encode Thumb-2 32-bit AND (register) - raw version
6607    fn encode_thumb32_and_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6608        // AND.W Rd, Rn, Rm
6609        // EA00 Rn | 0 Rd 00 00 Rm
6610        let hw1: u16 = (0xEA00 | rn) as u16;
6611        let hw2: u16 = ((rd << 8) | rm) as u16;
6612
6613        let mut bytes = hw1.to_le_bytes().to_vec();
6614        bytes.extend_from_slice(&hw2.to_le_bytes());
6615        Ok(bytes)
6616    }
6617
6618    /// Encode Thumb-2 32-bit AND with immediate - raw version
6619    fn encode_thumb32_and_imm_raw(&self, rd: u32, rn: u32, imm: u32) -> Result<Vec<u8>> {
6620        // AND.W Rd, Rn, #<modified_immediate>
6621        // For small immediates (0-255), the encoding is simpler
6622        // F0 00 Rn | 0 imm3 Rd imm8
6623        let i_bit = (imm >> 11) & 1;
6624        let imm3 = (imm >> 8) & 0x7;
6625        let imm8 = imm & 0xFF;
6626
6627        let hw1: u16 = (0xF000 | (i_bit << 10) | rn) as u16;
6628        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6629
6630        let mut bytes = hw1.to_le_bytes().to_vec();
6631        bytes.extend_from_slice(&hw2.to_le_bytes());
6632        Ok(bytes)
6633    }
6634
6635    /// Encode Thumb-2 32-bit SUB (register) - raw version
6636    fn encode_thumb32_sub_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6637        // SUB.W Rd, Rn, Rm
6638        // EBA0 Rn | 0 Rd 00 00 Rm
6639        let hw1: u16 = (0xEBA0 | rn) as u16;
6640        let hw2: u16 = ((rd << 8) | rm) as u16;
6641
6642        let mut bytes = hw1.to_le_bytes().to_vec();
6643        bytes.extend_from_slice(&hw2.to_le_bytes());
6644        Ok(bytes)
6645    }
6646
6647    /// Encode Thumb-2 32-bit ADD (register) - raw version
6648    fn encode_thumb32_add_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6649        // ADD.W Rd, Rn, Rm
6650        // EB00 Rn | 0 Rd 00 00 Rm
6651        let hw1: u16 = (0xEB00 | rn) as u16;
6652        let hw2: u16 = ((rd << 8) | rm) as u16;
6653
6654        let mut bytes = hw1.to_le_bytes().to_vec();
6655        bytes.extend_from_slice(&hw2.to_le_bytes());
6656        Ok(bytes)
6657    }
6658
6659    /// Encode Thumb-2 32-bit ADDS (register, flag-setting) - raw version.
6660    /// Used as the high-register fallback for `ArmOp::Adds` (i64 low-word add)
6661    /// so R8-R11 pair operands don't overflow the 16-bit field — #178/#180.
6662    fn encode_thumb32_adds_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6663        // ADDS.W Rd, Rn, Rm (T3, S=1): EB10 Rn | 0 Rd 00 00 Rm
6664        let hw1: u16 = (0xEB10 | rn) as u16;
6665        let hw2: u16 = ((rd << 8) | rm) as u16;
6666        let mut bytes = hw1.to_le_bytes().to_vec();
6667        bytes.extend_from_slice(&hw2.to_le_bytes());
6668        Ok(bytes)
6669    }
6670
6671    /// Encode Thumb-2 32-bit SUBS (register, flag-setting) - raw version.
6672    /// High-register fallback for `ArmOp::Subs` (i64 low-word subtract) — #178/#180.
6673    fn encode_thumb32_subs_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6674        // SUBS.W Rd, Rn, Rm (T3, S=1): EBB0 Rn | 0 Rd 00 00 Rm
6675        let hw1: u16 = (0xEBB0 | rn) as u16;
6676        let hw2: u16 = ((rd << 8) | rm) as u16;
6677        let mut bytes = hw1.to_le_bytes().to_vec();
6678        bytes.extend_from_slice(&hw2.to_le_bytes());
6679        Ok(bytes)
6680    }
6681
6682    /// Encode a sequence of ARM instructions
6683    pub fn encode_sequence(&self, ops: &[ArmOp]) -> Result<Vec<u8>> {
6684        let mut code = Vec::new();
6685
6686        for op in ops {
6687            let encoded = self.encode(op)?;
6688            code.extend_from_slice(&encoded);
6689        }
6690
6691        Ok(code)
6692    }
6693}
6694
6695/// Convert register to bit encoding (0-15)
6696/// Reverse of the ARMv7-M `ThumbExpandImm`: given a 32-bit immediate, return the
6697/// 12-bit `i:imm3:imm8` field if it is a representable modified immediate, else
6698/// `None` (the caller must materialize the value into a register). This is the
6699/// shared correct path for the data-processing immediate encoders — without it
6700/// they pack raw bits and silently mis-encode any value `> 0xFF` that isn't a
6701/// modified immediate (the silent-miscompile class behind #251/#253/#255).
6702fn try_thumb_expand_imm(value: u32) -> Option<u32> {
6703    // i:imm3 = 0000 → 8-bit value, zero-extended (00000000 00000000 00000000 XY).
6704    if value <= 0xFF {
6705        return Some(value);
6706    }
6707    let b0 = value & 0xFF; // byte 0
6708    let b1 = (value >> 8) & 0xFF; // byte 1
6709    // 0x00XY00XY (i:imm3 = 0001) — XY in bytes 0 and 2
6710    if value == (b0 << 16) | b0 {
6711        return Some(0x100 | b0);
6712    }
6713    // 0xXY00XY00 (i:imm3 = 0010) — XY in bytes 1 and 3
6714    if value == (b1 << 24) | (b1 << 8) {
6715        return Some(0x200 | b1);
6716    }
6717    // 0xXYXYXYXY (i:imm3 = 0011) — XY in all four bytes
6718    if value == (b0 << 24) | (b0 << 16) | (b0 << 8) | b0 {
6719        return Some(0x300 | b0);
6720    }
6721    // An 8-bit value with bit 7 set, rotated right by 8..=31. `rotate_left(rot)`
6722    // undoes the encoded right rotation; if the result is `1bbbbbbb` (0x80..=0xFF)
6723    // the value is representable. imm12[11:7] = rot, imm12[6:0] = low 7 bits.
6724    for rot in 8..=31u32 {
6725        let unrot = value.rotate_left(rot);
6726        if (0x80..=0xFF).contains(&unrot) {
6727            return Some((rot << 7) | (unrot & 0x7F));
6728        }
6729    }
6730    None
6731}
6732
6733/// Guard a Thumb-2 `LDR/STR Rd, [Rn, #imm12]` offset. The imm12 form supports
6734/// `0..=4095`; a larger offset must be materialized into a register by the
6735/// selector (register-offset addressing). Returning `Err` rather than silently
6736/// masking `offset & 0xFFF` closes the wrong-address miscompile class (#259,
6737/// the load/store sibling of #253/#255).
6738fn check_ldst_imm12(offset: u32) -> Result<()> {
6739    if offset > 0xFFF {
6740        Err(synth_core::Error::synthesis(
6741            "load/store immediate offset > 0xFFF (4095) — materialize the offset into a register",
6742        ))
6743    } else {
6744        Ok(())
6745    }
6746}
6747
6748fn reg_to_bits(reg: &Reg) -> u32 {
6749    match reg {
6750        Reg::R0 => 0,
6751        Reg::R1 => 1,
6752        Reg::R2 => 2,
6753        Reg::R3 => 3,
6754        Reg::R4 => 4,
6755        Reg::R5 => 5,
6756        Reg::R6 => 6,
6757        Reg::R7 => 7,
6758        Reg::R8 => 8,
6759        Reg::R9 => 9,
6760        Reg::R10 => 10,
6761        Reg::R11 => 11,
6762        Reg::R12 => 12,
6763        Reg::SP => 13,
6764        Reg::LR => 14,
6765        Reg::PC => 15,
6766    }
6767}
6768
6769/// Fallible form of the `verify_reg_bits` contract. PC (R15) is not a valid
6770/// data operand for the Thumb-2 encodings that use this guard (SDIV/UDIV/MLS/…
6771/// are UNPREDICTABLE with PC). Synth's own codegen never emits PC there, but
6772/// the encoder must stay *total* over arbitrary `ArmOp` inputs — the fuzz
6773/// harness (`encoder_no_panic`) requires Ok-or-Err, never a panic. Pre-fix, the
6774/// `debug_assert` in `verify_reg_bits` aborted under `-Cdebug-assertions`.
6775/// Returns a typed Err instead. See #185.
6776fn reg_bits_checked(bits: u32) -> Result<()> {
6777    if bits > 14 {
6778        return Err(synth_core::Error::synthesis(format!(
6779            "register bits {bits} (PC/R15) is not a valid operand for this Thumb-2 encoding"
6780        )));
6781    }
6782    Ok(())
6783}
6784
6785/// Try to encode a 32-bit value as an ARM rotated immediate (imm8 ROR 2*rot4).
6786/// Returns Some((encoded_bits, 1)) if representable, None otherwise.
6787fn try_encode_rotated_imm(val: u32) -> Option<(u32, u32)> {
6788    if val == 0 {
6789        return Some((0, 1));
6790    }
6791    for rot in 0..16u32 {
6792        let shift = rot * 2;
6793        // Rotate left by shift (undo the ROR) to see if result fits in 8 bits
6794        let unrotated = val.rotate_left(shift);
6795        if unrotated <= 0xFF {
6796            // Encoded as: rot4(4 bits) | imm8(8 bits) = rotate_imm << 8 | imm8
6797            return Some(((rot << 8) | unrotated, 1));
6798        }
6799    }
6800    None
6801}
6802
6803/// Encode operand2 field and return (bits, immediate_flag).
6804/// For ARM32 mode, immediates use the rotated-immediate encoding (imm8 ROR 2*rot4).
6805/// Panics if an immediate value cannot be represented. Callers that need large
6806/// immediates should use MOVW/MOVT instead of Operand2::Imm.
6807fn encode_operand2(op2: &Operand2) -> (u32, u32) {
6808    match op2 {
6809        Operand2::Imm(val) => {
6810            let uval = *val as u32;
6811            // Attempt rotated-immediate encoding (ARM32 Operand2)
6812            if let Some(encoded) = try_encode_rotated_imm(uval) {
6813                encoded
6814            } else {
6815                // Fallback: mask to 8 bits (legacy behavior for values that
6816                // cannot be represented). This should not be reached for
6817                // correctly-selected instructions; the instruction selector
6818                // must use MOVW/MOVT for large constants.
6819                let imm = uval & 0xFF;
6820                (imm, 1)
6821            }
6822        }
6823
6824        Operand2::Reg(reg) => {
6825            let reg_bits = reg_to_bits(reg);
6826            (reg_bits, 0) // I=0 for register
6827        }
6828
6829        Operand2::RegShift {
6830            rm,
6831            shift: _,
6832            amount,
6833        } => {
6834            // Simplified encoding with shift
6835            let rm_bits = reg_to_bits(rm);
6836            let shift_bits = (*amount & 0x1F) << 7;
6837            (shift_bits | rm_bits, 0)
6838        }
6839    }
6840}
6841
6842/// Encode memory address to (base_reg, offset)
6843fn encode_mem_addr(addr: &MemAddr) -> (u32, u32) {
6844    let base_bits = reg_to_bits(&addr.base);
6845    let offset_bits = (addr.offset as u32) & 0xFFF; // 12-bit offset
6846    (base_bits, offset_bits)
6847}
6848
6849/// S-register number: S0=0, S1=1, ..., S31=31
6850fn vfp_sreg_to_num(reg: &VfpReg) -> Result<u32> {
6851    match reg {
6852        VfpReg::S0 => Ok(0),
6853        VfpReg::S1 => Ok(1),
6854        VfpReg::S2 => Ok(2),
6855        VfpReg::S3 => Ok(3),
6856        VfpReg::S4 => Ok(4),
6857        VfpReg::S5 => Ok(5),
6858        VfpReg::S6 => Ok(6),
6859        VfpReg::S7 => Ok(7),
6860        VfpReg::S8 => Ok(8),
6861        VfpReg::S9 => Ok(9),
6862        VfpReg::S10 => Ok(10),
6863        VfpReg::S11 => Ok(11),
6864        VfpReg::S12 => Ok(12),
6865        VfpReg::S13 => Ok(13),
6866        VfpReg::S14 => Ok(14),
6867        VfpReg::S15 => Ok(15),
6868        VfpReg::S16 => Ok(16),
6869        VfpReg::S17 => Ok(17),
6870        VfpReg::S18 => Ok(18),
6871        VfpReg::S19 => Ok(19),
6872        VfpReg::S20 => Ok(20),
6873        VfpReg::S21 => Ok(21),
6874        VfpReg::S22 => Ok(22),
6875        VfpReg::S23 => Ok(23),
6876        VfpReg::S24 => Ok(24),
6877        VfpReg::S25 => Ok(25),
6878        VfpReg::S26 => Ok(26),
6879        VfpReg::S27 => Ok(27),
6880        VfpReg::S28 => Ok(28),
6881        VfpReg::S29 => Ok(29),
6882        VfpReg::S30 => Ok(30),
6883        VfpReg::S31 => Ok(31),
6884        // D-registers are not used in F32 single-precision encodings
6885        _ => Err(synth_core::Error::SynthesisError(
6886            "D-register not supported in single-precision VFP encoding".to_string(),
6887        )),
6888    }
6889}
6890
6891/// D-register number: D0=0, D1=1, ..., D15=15
6892fn vfp_dreg_to_num(reg: &VfpReg) -> Result<u32> {
6893    match reg {
6894        VfpReg::D0 => Ok(0),
6895        VfpReg::D1 => Ok(1),
6896        VfpReg::D2 => Ok(2),
6897        VfpReg::D3 => Ok(3),
6898        VfpReg::D4 => Ok(4),
6899        VfpReg::D5 => Ok(5),
6900        VfpReg::D6 => Ok(6),
6901        VfpReg::D7 => Ok(7),
6902        VfpReg::D8 => Ok(8),
6903        VfpReg::D9 => Ok(9),
6904        VfpReg::D10 => Ok(10),
6905        VfpReg::D11 => Ok(11),
6906        VfpReg::D12 => Ok(12),
6907        VfpReg::D13 => Ok(13),
6908        VfpReg::D14 => Ok(14),
6909        VfpReg::D15 => Ok(15),
6910        // S-registers are not used in F64 double-precision encodings
6911        _ => Err(synth_core::Error::SynthesisError(
6912            "S-register not supported in double-precision VFP encoding".to_string(),
6913        )),
6914    }
6915}
6916
6917/// Split S-register into (Vx[3:0], qualifier_bit) for VFP encoding.
6918/// For an S-register number s: Vx = s >> 1, qualifier = s & 1.
6919/// The qualifier bit goes to D (bit 22), N (bit 7), or M (bit 5) depending on role.
6920fn encode_sreg(s: u32) -> (u32, u32) {
6921    (s >> 1, s & 1)
6922}
6923
6924/// Split D-register into (Vx[3:0], qualifier_bit) for VFP double-precision encoding.
6925/// For a D-register number d: Vx = d & 0xF, qualifier = (d >> 4) & 1.
6926/// For D0-D15, qualifier is always 0.
6927fn encode_dreg(d: u32) -> (u32, u32) {
6928    (d & 0xF, (d >> 4) & 1)
6929}
6930
6931/// Encode a VFP 3-register arithmetic instruction (VADD.F32, VSUB.F32, VMUL.F32, VDIV.F32).
6932/// Returns the full 32-bit instruction word.
6933///
6934/// VFP encoding: [cond 1110] [D opc1 Vn] [Vd 101 sz] [N opc2 M 0 Vm]
6935/// For single-precision (sz=0), coprocessor = 0xA (bits[11:8]).
6936fn encode_vfp_3reg(base: u32, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<u32> {
6937    let sd_num = vfp_sreg_to_num(sd)?;
6938    let sn_num = vfp_sreg_to_num(sn)?;
6939    let sm_num = vfp_sreg_to_num(sm)?;
6940    let (vd, d) = encode_sreg(sd_num);
6941    let (vn, n) = encode_sreg(sn_num);
6942    let (vm, m) = encode_sreg(sm_num);
6943
6944    Ok(base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm)
6945}
6946
6947/// Encode a VFP 2-register instruction (VNEG.F32, VABS.F32, VSQRT.F32).
6948/// Returns the full 32-bit instruction word.
6949fn encode_vfp_2reg(base: u32, sd: &VfpReg, sm: &VfpReg) -> Result<u32> {
6950    let sd_num = vfp_sreg_to_num(sd)?;
6951    let sm_num = vfp_sreg_to_num(sm)?;
6952    let (vd, d) = encode_sreg(sd_num);
6953    let (vm, m) = encode_sreg(sm_num);
6954
6955    Ok(base | (d << 22) | (vd << 12) | (m << 5) | vm)
6956}
6957
6958/// Encode a VFP load/store (VLDR.F32 / VSTR.F32).
6959/// offset is in bytes and must be word-aligned; encoded as imm8 = offset/4.
6960/// U bit (bit 23) controls add/subtract offset.
6961fn encode_vfp_ldst(base: u32, sd: &VfpReg, addr: &MemAddr) -> Result<u32> {
6962    let sd_num = vfp_sreg_to_num(sd)?;
6963    let (vd, d) = encode_sreg(sd_num);
6964    let rn = reg_to_bits(&addr.base);
6965
6966    let offset = addr.offset;
6967    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
6968    let abs_offset = offset.unsigned_abs();
6969    let imm8 = (abs_offset / 4) & 0xFF;
6970
6971    Ok(base | (u_bit << 23) | (d << 22) | (rn << 16) | (vd << 12) | imm8)
6972}
6973
6974/// Encode VMOV between core register and S-register.
6975/// VMOV Sn, Rt: 0xEE00_0A10 | (Vn << 16) | (N << 7) | (Rt << 12)
6976/// VMOV Rt, Sn: 0xEE10_0A10 | (Vn << 16) | (N << 7) | (Rt << 12)
6977fn encode_vmov_core_sreg(to_sreg: bool, sreg: &VfpReg, core: &Reg) -> Result<u32> {
6978    let s_num = vfp_sreg_to_num(sreg)?;
6979    let (vn, n) = encode_sreg(s_num);
6980    let rt = reg_to_bits(core);
6981
6982    let base = if to_sreg { 0xEE000A10 } else { 0xEE100A10 };
6983    Ok(base | (vn << 16) | (rt << 12) | (n << 7))
6984}
6985
6986/// Encode a VFP 3-register double-precision instruction (VADD.F64, VSUB.F64, etc.).
6987/// For double-precision (sz=1), coprocessor = 0xB (bits[11:8]).
6988/// The base should have bit 8 = 1 for F64 (0xB suffix instead of 0xA).
6989fn encode_vfp_3reg_f64(base: u32, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<u32> {
6990    let dd_num = vfp_dreg_to_num(dd)?;
6991    let dn_num = vfp_dreg_to_num(dn)?;
6992    let dm_num = vfp_dreg_to_num(dm)?;
6993    let (vd, d) = encode_dreg(dd_num);
6994    let (vn, n) = encode_dreg(dn_num);
6995    let (vm, m) = encode_dreg(dm_num);
6996
6997    Ok(base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm)
6998}
6999
7000/// Encode a VFP 2-register double-precision instruction (VNEG.F64, VABS.F64, VSQRT.F64).
7001fn encode_vfp_2reg_f64(base: u32, dd: &VfpReg, dm: &VfpReg) -> Result<u32> {
7002    let dd_num = vfp_dreg_to_num(dd)?;
7003    let dm_num = vfp_dreg_to_num(dm)?;
7004    let (vd, d) = encode_dreg(dd_num);
7005    let (vm, m) = encode_dreg(dm_num);
7006
7007    Ok(base | (d << 22) | (vd << 12) | (m << 5) | vm)
7008}
7009
7010/// Encode a VFP load/store for double-precision (VLDR.64 / VSTR.64).
7011/// offset is in bytes and must be word-aligned; encoded as imm8 = offset/4.
7012fn encode_vfp_ldst_f64(base: u32, dd: &VfpReg, addr: &MemAddr) -> Result<u32> {
7013    let dd_num = vfp_dreg_to_num(dd)?;
7014    let (vd, d) = encode_dreg(dd_num);
7015    let rn = reg_to_bits(&addr.base);
7016
7017    let offset = addr.offset;
7018    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7019    let abs_offset = offset.unsigned_abs();
7020    let imm8 = (abs_offset / 4) & 0xFF;
7021
7022    Ok(base | (u_bit << 23) | (d << 22) | (rn << 16) | (vd << 12) | imm8)
7023}
7024
7025/// Encode VMOV between two core registers and a D-register.
7026/// VMOV Dm, Rt, Rt2: 0xEC40_0B10 | (Rt2 << 16) | (Rt << 12) | (M << 5) | Vm
7027/// VMOV Rt, Rt2, Dm: 0xEC50_0B10 | (Rt2 << 16) | (Rt << 12) | (M << 5) | Vm
7028fn encode_vmov_core_dreg(
7029    to_dreg: bool,
7030    dreg: &VfpReg,
7031    core_lo: &Reg,
7032    core_hi: &Reg,
7033) -> Result<u32> {
7034    let d_num = vfp_dreg_to_num(dreg)?;
7035    let (vm, m) = encode_dreg(d_num);
7036    let rt = reg_to_bits(core_lo);
7037    let rt2 = reg_to_bits(core_hi);
7038
7039    let base = if to_dreg { 0xEC400B10 } else { 0xEC500B10 };
7040    Ok(base | (rt2 << 16) | (rt << 12) | (m << 5) | vm)
7041}
7042
7043/// Emit a VFP 32-bit instruction as Thumb-2 bytes (two LE halfwords).
7044fn vfp_to_thumb_bytes(instr: u32) -> Vec<u8> {
7045    let hw1 = ((instr >> 16) & 0xFFFF) as u16;
7046    let hw2 = (instr & 0xFFFF) as u16;
7047    let mut bytes = hw1.to_le_bytes().to_vec();
7048    bytes.extend_from_slice(&hw2.to_le_bytes());
7049    bytes
7050}
7051
7052// ============================================================================
7053// Helium MVE encoding helpers
7054// ============================================================================
7055
7056/// Q-register number: Q0=0, Q1=1, ..., Q7=7
7057fn qreg_to_num(reg: &QReg) -> u32 {
7058    match reg {
7059        QReg::Q0 => 0,
7060        QReg::Q1 => 1,
7061        QReg::Q2 => 2,
7062        QReg::Q3 => 3,
7063        QReg::Q4 => 4,
7064        QReg::Q5 => 5,
7065        QReg::Q6 => 6,
7066        QReg::Q7 => 7,
7067    }
7068}
7069
7070/// MVE element size to encoding bits: S8=0b00, S16=0b01, S32=0b10
7071fn mve_size_bits(size: &MveSize) -> u32 {
7072    match size {
7073        MveSize::S8 => 0b00,
7074        MveSize::S16 => 0b01,
7075        MveSize::S32 => 0b10,
7076    }
7077}
7078
7079/// Encode MVE 3-register instruction.
7080/// Q-registers are encoded as D-register pairs: Q0=D0:D1, Q1=D2:D3, etc.
7081/// In NEON/MVE encoding, the Q-register uses D-register number = Qn * 2.
7082fn encode_mve_3reg(base: u32, qd: &QReg, qn: &QReg, qm: &QReg) -> u32 {
7083    let d = qreg_to_num(qd) * 2;
7084    let n = qreg_to_num(qn) * 2;
7085    let m = qreg_to_num(qm) * 2;
7086
7087    // Standard NEON/MVE 3-register encoding:
7088    // D bit (bit 22) = Vd[4], Vd[3:0] = bits [15:12]
7089    // N bit (bit 7)  = Vn[4], Vn[3:0] = bits [19:16]
7090    // M bit (bit 5)  = Vm[4], Vm[3:0] = bits [3:0]
7091    let vd = d & 0xF;
7092    let d_bit = (d >> 4) & 1;
7093    let vn = n & 0xF;
7094    let n_bit = (n >> 4) & 1;
7095    let vm = m & 0xF;
7096    let m_bit = (m >> 4) & 1;
7097
7098    base | (d_bit << 22) | (vn << 16) | (vd << 12) | (n_bit << 7) | (m_bit << 5) | vm
7099}
7100
7101/// Encode MVE 3-register bitwise instruction (VAND, VORR, VEOR, VBIC).
7102fn encode_mve_3reg_bitwise(base: u32, qd: &QReg, qn: &QReg, qm: &QReg) -> u32 {
7103    encode_mve_3reg(base, qd, qn, qm)
7104}
7105
7106/// Encode MVE VLDRW.32 Qd, [Rn, #offset]
7107/// Format: EC9x xxxx - contiguous load, word-sized elements
7108fn encode_mve_vldrw(qd: &QReg, addr: &MemAddr) -> u32 {
7109    let qd_enc = qreg_to_num(qd) * 2;
7110    let rn = reg_to_bits(&addr.base);
7111    let offset = addr.offset;
7112    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7113    let abs_offset = offset.unsigned_abs();
7114    let imm7 = (abs_offset / 4) & 0x7F; // 7-bit word-aligned offset
7115
7116    // VLDRW.32 Qd, [Rn, #imm]: ED10 xx80 variant
7117    0xED100E80
7118        | (u_bit << 23)
7119        | ((qd_enc >> 4) << 22)
7120        | (rn << 16)
7121        | ((qd_enc & 0xF) << 12)
7122        | (imm7 & 0x7F)
7123}
7124
7125/// Encode MVE VSTRW.32 Qd, [Rn, #offset]
7126fn encode_mve_vstrw(qd: &QReg, addr: &MemAddr) -> u32 {
7127    let qd_enc = qreg_to_num(qd) * 2;
7128    let rn = reg_to_bits(&addr.base);
7129    let offset = addr.offset;
7130    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7131    let abs_offset = offset.unsigned_abs();
7132    let imm7 = (abs_offset / 4) & 0x7F;
7133
7134    0xED000E80
7135        | (u_bit << 23)
7136        | ((qd_enc >> 4) << 22)
7137        | (rn << 16)
7138        | ((qd_enc & 0xF) << 12)
7139        | (imm7 & 0x7F)
7140}
7141
7142impl ArmEncoder {
7143    /// Encode MVE constant load: MOVW+MOVT+VMOV for each 32-bit word, then assemble Q-register
7144    fn encode_thumb_mve_const(&self, qd: &QReg, bytes: &[u8; 16]) -> Result<Vec<u8>> {
7145        let mut result = Vec::new();
7146        let qd_num = qreg_to_num(qd);
7147
7148        // Load each 32-bit word into R12 (temp) then VMOV into S-register
7149        for i in 0..4 {
7150            let word = u32::from_le_bytes([
7151                bytes[i * 4],
7152                bytes[i * 4 + 1],
7153                bytes[i * 4 + 2],
7154                bytes[i * 4 + 3],
7155            ]);
7156            let lo16 = word & 0xFFFF;
7157            let hi16 = (word >> 16) & 0xFFFF;
7158
7159            // MOVW R12, #lo16
7160            result.extend_from_slice(&self.encode_thumb32_movw_raw(12, lo16)?);
7161            // MOVT R12, #hi16
7162            if hi16 != 0 {
7163                result.extend_from_slice(&self.encode_thumb32_movt_raw(12, hi16)?);
7164            }
7165
7166            // VMOV Sn, R12 where Sn = Qd*4 + i
7167            let s_num = qd_num * 4 + i as u32;
7168            let (vn, n) = encode_sreg(s_num);
7169            let vmov: u32 = 0xEE000A10 | (vn << 16) | (12 << 12) | (n << 7);
7170            result.extend_from_slice(&vfp_to_thumb_bytes(vmov));
7171        }
7172
7173        Ok(result)
7174    }
7175
7176    /// Encode lane-wise f32 binary operation (VDIV, etc.) via S-register extraction
7177    fn encode_thumb_mve_lane_wise_f32_binop(
7178        &self,
7179        qd: &QReg,
7180        qn: &QReg,
7181        qm: &QReg,
7182        vfp_base: u32,
7183    ) -> Result<Vec<u8>> {
7184        let mut result = Vec::new();
7185        let qd_num = qreg_to_num(qd);
7186        let qn_num = qreg_to_num(qn);
7187        let qm_num = qreg_to_num(qm);
7188
7189        // For each lane 0..3: use S-registers directly (Q aliasing)
7190        for i in 0..4u32 {
7191            let sd = qd_num * 4 + i;
7192            let sn = qn_num * 4 + i;
7193            let sm = qm_num * 4 + i;
7194
7195            let (vd, d) = encode_sreg(sd);
7196            let (vn, n) = encode_sreg(sn);
7197            let (vm, m) = encode_sreg(sm);
7198
7199            let instr = vfp_base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm;
7200            result.extend_from_slice(&vfp_to_thumb_bytes(instr));
7201        }
7202
7203        Ok(result)
7204    }
7205
7206    /// Encode lane-wise f32 VSQRT via S-register extraction
7207    fn encode_thumb_mve_lane_wise_f32_sqrt(&self, qd: &QReg, qm: &QReg) -> Result<Vec<u8>> {
7208        let mut result = Vec::new();
7209        let qd_num = qreg_to_num(qd);
7210        let qm_num = qreg_to_num(qm);
7211
7212        // VSQRT.F32 base: 0xEEB10AC0
7213        for i in 0..4u32 {
7214            let sd = qd_num * 4 + i;
7215            let sm = qm_num * 4 + i;
7216
7217            let (vd, d) = encode_sreg(sd);
7218            let (vm, m) = encode_sreg(sm);
7219
7220            let instr: u32 = 0xEEB10AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
7221            result.extend_from_slice(&vfp_to_thumb_bytes(instr));
7222        }
7223
7224        Ok(result)
7225    }
7226}
7227
7228#[cfg(test)]
7229mod tests {
7230    use super::*;
7231
7232    #[test]
7233    fn test_encoder_creation() {
7234        let encoder_arm = ArmEncoder::new_arm32();
7235        assert!(!encoder_arm.thumb_mode);
7236
7237        let encoder_thumb = ArmEncoder::new_thumb2();
7238        assert!(encoder_thumb.thumb_mode);
7239    }
7240
7241    /// #204 WAKE-path regression: `SetCond` materialized 0/1 with the 16-bit
7242    /// `MOVS Rd,#imm` (T1), whose Rd field is 3 bits (R0–R7). For a high Rd
7243    /// (R8–R12) `rd_bits << 8` overflows bit 11, flipping the opcode MOVS→CMP
7244    /// (`0x2c00`), so the boolean was never written — gale's `has_waiter` kept a
7245    /// stale value and the binary-sem WAKE dispatch read garbage. High Rd must
7246    /// use the 32-bit `MOV.W` (T2). Verify the bytes, not the IR.
7247    #[test]
7248    fn test_encode_setcond_high_reg_uses_mov_w_204() {
7249        use synth_synthesis::{ArmOp, Condition, Reg};
7250        let enc = ArmEncoder::new_thumb2();
7251        // R12 (high): must be ITE + MOV.W #1 + MOV.W #0, never a 16-bit MOVS/CMP.
7252        let hi = enc
7253            .encode(&ArmOp::SetCond {
7254                rd: Reg::R12,
7255                cond: Condition::NE,
7256            })
7257            .unwrap();
7258        assert_eq!(hi.len(), 10, "ITE(2) + MOV.W(4) + MOV.W(4): {hi:02x?}");
7259        // both value halfwords are MOV.W (0xF04F) — NOT the corrupt CMP (0x2c..).
7260        assert_eq!(&hi[2..4], &[0x4F, 0xF0], "then = MOV.W: {hi:02x?}");
7261        assert_eq!(&hi[6..8], &[0x4F, 0xF0], "else = MOV.W: {hi:02x?}");
7262        assert_eq!(hi[4] & 0x0F, 0x01, "then imm = #1");
7263        assert_eq!(hi[8] & 0x0F, 0x00, "else imm = #0");
7264        // Low Rd keeps the compact 16-bit MOVS form.
7265        let lo = enc
7266            .encode(&ArmOp::SetCond {
7267                rd: Reg::R0,
7268                cond: Condition::NE,
7269            })
7270            .unwrap();
7271        assert_eq!(lo.len(), 6, "ITE(2) + MOVS(2) + MOVS(2): {lo:02x?}");
7272        assert_eq!(lo[2..4], [0x01, 0x20], "then = MOVS R0,#1");
7273        assert_eq!(lo[4..6], [0x00, 0x20], "else = MOVS R0,#0");
7274    }
7275
7276    /// #209 Opt 1b: UMULL RdLo, RdHi, Rn, Rm encodes correctly on both ISAs.
7277    /// Thumb-2 T1: 1111 1011 1010 Rn | RdLo RdHi 0000 Rm.
7278    /// A32:        cond 0000 1000 RdHi RdLo Rm 1001 Rn.
7279    #[test]
7280    fn test_encode_umull_209b() {
7281        use synth_synthesis::{ArmOp, Reg};
7282        let op = ArmOp::Umull {
7283            rdlo: Reg::R4,
7284            rdhi: Reg::R5,
7285            rn: Reg::R0,
7286            rm: Reg::R3,
7287        };
7288        // Thumb-2: hw1 = 0xFBA0 | 0 = 0xFBA0; hw2 = (4<<12)|(5<<8)|3 = 0x4503.
7289        let t = ArmEncoder::new_thumb2().encode(&op).unwrap();
7290        assert_eq!(
7291            t,
7292            vec![0xA0, 0xFB, 0x03, 0x45],
7293            "umull r4,r5,r0,r3 (T2): {t:02x?}"
7294        );
7295        // A32: 0xE0800090 | (5<<16) | (4<<12) | (3<<8) | 0 = 0xE0854390.
7296        let a = ArmEncoder::new_arm32().encode(&op).unwrap();
7297        assert_eq!(
7298            a,
7299            0xE085_4390u32.to_le_bytes().to_vec(),
7300            "umull (A32): {a:02x?}"
7301        );
7302    }
7303
7304    /// #206 regression: the ARM32 (A32) `Ldr`/`Str` encoders fed `addr` through
7305    /// `encode_mem_addr`, which returns only the 12-bit immediate — so a register
7306    /// offset (`[rn, rm, #off]`) was silently dropped to `[rn, #off]`, sending
7307    /// the access to the wrong runtime address (silent miscompile on the default
7308    /// `--target arm`). A register offset must materialize `ip = rn + rm` and
7309    /// load from `[ip, #off]`. Verify the bytes.
7310    #[test]
7311    fn test_encode_arm32_indexed_load_keeps_index_206() {
7312        use synth_synthesis::{ArmOp, MemAddr, Reg};
7313        let enc = ArmEncoder::new_arm32();
7314        // ldr r0, [r11, r1, #8]  must NOT collapse to a single immediate ldr.
7315        let bytes = enc
7316            .encode(&ArmOp::Ldr {
7317                rd: Reg::R0,
7318                addr: MemAddr::reg_imm(Reg::R11, Reg::R1, 8),
7319            })
7320            .unwrap();
7321        assert_eq!(
7322            bytes.len(),
7323            8,
7324            "expected ADD ip + LDR (2 words): {bytes:02x?}"
7325        );
7326        let add = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
7327        let ldr = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
7328        // ADD ip, r11, r1  = 0xE08BC001
7329        assert_eq!(add, 0xE08B_C001, "ADD ip,r11,r1: {add:#010x}");
7330        // LDR r0, [ip, #8] = 0xE59C0008
7331        assert_eq!(ldr, 0xE59C_0008, "LDR r0,[ip,#8]: {ldr:#010x}");
7332        // A bare immediate ldr (the bug) would be 0xE59B0008 (base=r11) — reject.
7333        assert_ne!(ldr, 0xE59B_0008, "index must not be dropped");
7334    }
7335
7336    /// #178/#180 regression: the Thumb `Add`/`Adds`/`Subs` reg-forms used the
7337    /// 16-bit encoding unconditionally. For high registers (R12 base scratch,
7338    /// R8-R11 i64 pairs) the 3-bit register fields overflow and corrupt the
7339    /// operands — `add ip,ip,r0` came out as `adds r4,r5,r1` (0x186C), silently
7340    /// dropping the address operand and miscompiling every optimized memory
7341    /// access. High registers must use the 32-bit `.W` forms.
7342    #[test]
7343    fn test_encode_thumb_add_high_reg_uses_add_w_178_180() {
7344        let encoder = ArmEncoder::new_thumb2();
7345
7346        // add ip, ip, r0  — the exact MemLoad/MemStore base+addr op.
7347        let code = encoder
7348            .encode(&ArmOp::Add {
7349                rd: Reg::R12,
7350                rn: Reg::R12,
7351                op2: Operand2::Reg(Reg::R0),
7352            })
7353            .unwrap();
7354        // ADD.W ip, ip, r0 = EB0C 0C00 (little-endian halfwords).
7355        assert_eq!(
7356            code,
7357            vec![0x0C, 0xEB, 0x00, 0x0C],
7358            "high-reg Thumb ADD must be 32-bit ADD.W (EB0C 0C00), not corrupt 16-bit; got {code:02X?}"
7359        );
7360        // Must NOT be the buggy 16-bit 0x186C (`adds r4,r5,r1`).
7361        assert_ne!(code, vec![0x6C, 0x18], "regressed to corrupt 16-bit ADDS");
7362
7363        // Low-register add stays 16-bit (no regression for the common case).
7364        let lo = encoder
7365            .encode(&ArmOp::Add {
7366                rd: Reg::R1,
7367                rn: Reg::R2,
7368                op2: Operand2::Reg(Reg::R3),
7369            })
7370            .unwrap();
7371        assert_eq!(
7372            lo.len(),
7373            2,
7374            "low-reg ADD should remain 16-bit, got {lo:02X?}"
7375        );
7376    }
7377
7378    /// #178/#180 sibling: i64 low-word `Adds`/`Subs` can land in R8-R11 pairs;
7379    /// those must fall back to 32-bit ADDS.W/SUBS.W (flag-setting preserved).
7380    #[test]
7381    fn test_encode_thumb_adds_subs_high_reg_use_32bit_178_180() {
7382        let encoder = ArmEncoder::new_thumb2();
7383
7384        // adds r10, r10, r8  → ADDS.W = EB1A 0A08
7385        let adds = encoder
7386            .encode(&ArmOp::Adds {
7387                rd: Reg::R10,
7388                rn: Reg::R10,
7389                op2: Operand2::Reg(Reg::R8),
7390            })
7391            .unwrap();
7392        assert_eq!(
7393            adds,
7394            vec![0x1A, 0xEB, 0x08, 0x0A],
7395            "high-reg ADDS must be 32-bit ADDS.W (EB1A 0A08); got {adds:02X?}"
7396        );
7397
7398        // subs r10, r10, r8  → SUBS.W = EBBA 0A08
7399        let subs = encoder
7400            .encode(&ArmOp::Subs {
7401                rd: Reg::R10,
7402                rn: Reg::R10,
7403                op2: Operand2::Reg(Reg::R8),
7404            })
7405            .unwrap();
7406        assert_eq!(
7407            subs,
7408            vec![0xBA, 0xEB, 0x08, 0x0A],
7409            "high-reg SUBS must be 32-bit SUBS.W (EBBA 0A08); got {subs:02X?}"
7410        );
7411    }
7412
7413    /// #184 (sibling of #180): 16-bit CMN (T1) only encodes R0-R7. High registers
7414    /// must use 32-bit CMN.W, not the corrupt truncated 16-bit form.
7415    #[test]
7416    fn test_encode_thumb_cmn_high_reg_uses_cmn_w_184() {
7417        let encoder = ArmEncoder::new_thumb2();
7418
7419        // cmn r10, r8  → CMN.W = EB1A 0F08 (ADD.W S=1, Rd=PC discarded).
7420        let cmn = encoder
7421            .encode(&ArmOp::Cmn {
7422                rn: Reg::R10,
7423                op2: Operand2::Reg(Reg::R8),
7424            })
7425            .unwrap();
7426        assert_eq!(
7427            cmn,
7428            vec![0x1A, 0xEB, 0x08, 0x0F],
7429            "high-reg CMN must be 32-bit CMN.W (EB1A 0F08); got {cmn:02X?}"
7430        );
7431
7432        // Low registers stay 16-bit: cmn r1, r2 = 0x42D1.
7433        let lo = encoder
7434            .encode(&ArmOp::Cmn {
7435                rn: Reg::R1,
7436                op2: Operand2::Reg(Reg::R2),
7437            })
7438            .unwrap();
7439        assert_eq!(
7440            lo.len(),
7441            2,
7442            "low-reg CMN should remain 16-bit, got {lo:02X?}"
7443        );
7444        assert_eq!(lo, vec![0xD1, 0x42], "low-reg CMN bytes wrong: {lo:02X?}");
7445    }
7446
7447    /// #185 regression: feeding PC (R15) as a data operand to a Thumb-2 op that
7448    /// guards its registers must return Err, not panic under debug-assertions.
7449    /// (Synth never emits PC here; the fuzz harness requires encode() be total.)
7450    #[test]
7451    fn test_encode_pc_operand_returns_err_not_panic_185() {
7452        let encoder = ArmEncoder::new_thumb2();
7453        for op in [
7454            ArmOp::Sdiv {
7455                rd: Reg::PC,
7456                rn: Reg::R0,
7457                rm: Reg::R1,
7458            },
7459            ArmOp::Udiv {
7460                rd: Reg::R0,
7461                rn: Reg::PC,
7462                rm: Reg::R1,
7463            },
7464            ArmOp::Sdiv {
7465                rd: Reg::R0,
7466                rn: Reg::R1,
7467                rm: Reg::PC,
7468            },
7469        ] {
7470            let r = encoder.encode(&op);
7471            assert!(
7472                r.is_err(),
7473                "encode({op:?}) must return Err for a PC operand, got {r:?}"
7474            );
7475        }
7476        // Valid registers still encode fine (no false rejection).
7477        assert!(
7478            encoder
7479                .encode(&ArmOp::Sdiv {
7480                    rd: Reg::R0,
7481                    rn: Reg::R1,
7482                    rm: Reg::R2
7483                })
7484                .is_ok()
7485        );
7486    }
7487
7488    #[test]
7489    fn test_encode_nop_arm32() {
7490        let encoder = ArmEncoder::new_arm32();
7491        let code = encoder.encode(&ArmOp::Nop).unwrap();
7492
7493        assert_eq!(code.len(), 4); // ARM32 instructions are 4 bytes
7494        assert_eq!(code, vec![0x00, 0x00, 0xA0, 0xE1]); // MOV R0, R0
7495    }
7496
7497    #[test]
7498    fn test_encode_nop_thumb() {
7499        let encoder = ArmEncoder::new_thumb2();
7500        let code = encoder.encode(&ArmOp::Nop).unwrap();
7501
7502        assert_eq!(code.len(), 2); // Thumb instructions are 2 bytes
7503        assert_eq!(code, vec![0x00, 0xBF]); // NOP
7504    }
7505
7506    #[test]
7507    fn test_encode_mov_immediate_arm32() {
7508        let encoder = ArmEncoder::new_arm32();
7509        let op = ArmOp::Mov {
7510            rd: Reg::R0,
7511            op2: Operand2::Imm(42),
7512        };
7513
7514        let code = encoder.encode(&op).unwrap();
7515        assert_eq!(code.len(), 4);
7516
7517        // Verify it's a MOV instruction (bits should have immediate flag set)
7518        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7519        assert_eq!(instr & 0x0E000000, 0x02000000); // Check I bit is set
7520    }
7521
7522    #[test]
7523    fn test_encode_add_registers_arm32() {
7524        let encoder = ArmEncoder::new_arm32();
7525        let op = ArmOp::Add {
7526            rd: Reg::R0,
7527            rn: Reg::R1,
7528            op2: Operand2::Reg(Reg::R2),
7529        };
7530
7531        let code = encoder.encode(&op).unwrap();
7532        assert_eq!(code.len(), 4);
7533
7534        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7535        // Verify it's an ADD instruction with correct opcode
7536        assert_eq!(instr & 0x0FE00000, 0x00800000);
7537    }
7538
7539    #[test]
7540    fn test_encode_ldr_arm32() {
7541        let encoder = ArmEncoder::new_arm32();
7542        let op = ArmOp::Ldr {
7543            rd: Reg::R0,
7544            addr: MemAddr::imm(Reg::R1, 4),
7545        };
7546
7547        let code = encoder.encode(&op).unwrap();
7548        assert_eq!(code.len(), 4);
7549
7550        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7551        // Verify load bit is set
7552        assert_eq!(instr & 0x00100000, 0x00100000);
7553    }
7554
7555    #[test]
7556    fn test_encode_str_arm32() {
7557        let encoder = ArmEncoder::new_arm32();
7558        let op = ArmOp::Str {
7559            rd: Reg::R0,
7560            addr: MemAddr::imm(Reg::SP, 0),
7561        };
7562
7563        let code = encoder.encode(&op).unwrap();
7564        assert_eq!(code.len(), 4);
7565    }
7566
7567    #[test]
7568    fn test_encode_branch_arm32() {
7569        let encoder = ArmEncoder::new_arm32();
7570        let op = ArmOp::Bl {
7571            label: "main".to_string(),
7572        };
7573
7574        let code = encoder.encode(&op).unwrap();
7575        assert_eq!(code.len(), 4);
7576
7577        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7578        // Verify BL opcode
7579        assert_eq!(instr & 0x0F000000, 0x0B000000);
7580    }
7581
7582    /// Regression test for #167 + #174: the Thumb-2 BL relocatable placeholder
7583    /// must carry a -4 addend so an R_ARM_THM_CALL nets to exactly the symbol S.
7584    /// The correct encoding is what `gas` emits for `bl <extern>`: f7ff fffe
7585    /// (hw1=0xF7FF, hw2=0xFFFE), little-endian bytes FF F7 FE FF.
7586    ///   - 0xD000 (J1=J2=0) → ~+0x600000 garbage addend: `bl c0000c` / truncated
7587    ///     to fit (#167).
7588    ///   - 0xF800 (addend 0) → lands at S+4, one instruction past the callee
7589    ///     entry (#174).
7590    ///   - 0xFFFE (addend -4) → lands at S. Correct.
7591    #[test]
7592    fn test_encode_thumb_bl_placeholder_addend_167_174() {
7593        let encoder = ArmEncoder::new_thumb2();
7594        let op = ArmOp::Bl {
7595            label: "callee".to_string(),
7596        };
7597
7598        let code = encoder.encode(&op).unwrap();
7599        assert_eq!(code.len(), 4, "Thumb-2 BL is 32-bit");
7600
7601        let hw1 = u16::from_le_bytes([code[0], code[1]]);
7602        let hw2 = u16::from_le_bytes([code[2], code[3]]);
7603        assert_eq!(hw1, 0xF7FF, "BL first halfword (matches gas `bl <extern>`)");
7604        assert_eq!(
7605            hw2, 0xFFFE,
7606            "BL second halfword must be 0xFFFE (-4 addend → nets to S), not 0xF800 (→ S+4, #174) or 0xD000 (#167)"
7607        );
7608        assert_ne!(hw2, 0xF800, "0xF800 (addend 0) lands at S+4 (#174)");
7609        assert_ne!(hw2, 0xD000, "0xD000 bakes in a ~+0x600000 addend (#167)");
7610    }
7611
7612    #[test]
7613    fn test_encode_sequence() {
7614        let encoder = ArmEncoder::new_arm32();
7615        let ops = vec![
7616            ArmOp::Mov {
7617                rd: Reg::R0,
7618                op2: Operand2::Imm(42),
7619            },
7620            ArmOp::Mov {
7621                rd: Reg::R1,
7622                op2: Operand2::Imm(10),
7623            },
7624            ArmOp::Add {
7625                rd: Reg::R2,
7626                rn: Reg::R0,
7627                op2: Operand2::Reg(Reg::R1),
7628            },
7629        ];
7630
7631        let code = encoder.encode_sequence(&ops).unwrap();
7632        assert_eq!(code.len(), 12); // 3 instructions * 4 bytes
7633    }
7634
7635    #[test]
7636    fn test_reg_to_bits() {
7637        assert_eq!(reg_to_bits(&Reg::R0), 0);
7638        assert_eq!(reg_to_bits(&Reg::R7), 7);
7639        assert_eq!(reg_to_bits(&Reg::SP), 13);
7640        assert_eq!(reg_to_bits(&Reg::LR), 14);
7641        assert_eq!(reg_to_bits(&Reg::PC), 15);
7642    }
7643
7644    #[test]
7645    fn test_encode_bitwise_operations() {
7646        let encoder = ArmEncoder::new_arm32();
7647
7648        let and_op = ArmOp::And {
7649            rd: Reg::R0,
7650            rn: Reg::R1,
7651            op2: Operand2::Reg(Reg::R2),
7652        };
7653        let and_code = encoder.encode(&and_op).unwrap();
7654        assert_eq!(and_code.len(), 4);
7655
7656        let orr_op = ArmOp::Orr {
7657            rd: Reg::R0,
7658            rn: Reg::R1,
7659            op2: Operand2::Reg(Reg::R2),
7660        };
7661        let orr_code = encoder.encode(&orr_op).unwrap();
7662        assert_eq!(orr_code.len(), 4);
7663
7664        let eor_op = ArmOp::Eor {
7665            rd: Reg::R0,
7666            rn: Reg::R1,
7667            op2: Operand2::Reg(Reg::R2),
7668        };
7669        let eor_code = encoder.encode(&eor_op).unwrap();
7670        assert_eq!(eor_code.len(), 4);
7671    }
7672
7673    // === Thumb-2 32-bit encoding tests ===
7674
7675    #[test]
7676    fn test_encode_sdiv_thumb2() {
7677        let encoder = ArmEncoder::new_thumb2();
7678        let op = ArmOp::Sdiv {
7679            rd: Reg::R0,
7680            rn: Reg::R1,
7681            rm: Reg::R2,
7682        };
7683
7684        let code = encoder.encode(&op).unwrap();
7685        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7686
7687        // SDIV R0, R1, R2: 0xFB91 0xF0F2
7688        // First halfword: 0xFB90 | Rn(1) = 0xFB91
7689        // Second halfword: 0xF0F0 | Rd(0)<<8 | Rm(2) = 0xF0F2
7690        // Little-endian: [0x91, 0xFB, 0xF2, 0xF0]
7691        assert_eq!(code[0], 0x91);
7692        assert_eq!(code[1], 0xFB);
7693        assert_eq!(code[2], 0xF2);
7694        assert_eq!(code[3], 0xF0);
7695    }
7696
7697    #[test]
7698    fn test_encode_udiv_thumb2() {
7699        let encoder = ArmEncoder::new_thumb2();
7700        let op = ArmOp::Udiv {
7701            rd: Reg::R0,
7702            rn: Reg::R1,
7703            rm: Reg::R2,
7704        };
7705
7706        let code = encoder.encode(&op).unwrap();
7707        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7708
7709        // UDIV R0, R1, R2: 0xFBB1 0xF0F2
7710        // Little-endian: [0xB1, 0xFB, 0xF2, 0xF0]
7711        assert_eq!(code[0], 0xB1);
7712        assert_eq!(code[1], 0xFB);
7713        assert_eq!(code[2], 0xF2);
7714        assert_eq!(code[3], 0xF0);
7715    }
7716
7717    #[test]
7718    fn test_encode_mul_thumb2() {
7719        let encoder = ArmEncoder::new_thumb2();
7720        let op = ArmOp::Mul {
7721            rd: Reg::R0,
7722            rn: Reg::R1,
7723            rm: Reg::R2,
7724        };
7725
7726        let code = encoder.encode(&op).unwrap();
7727        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7728    }
7729
7730    #[test]
7731    fn test_encode_and_thumb2() {
7732        let encoder = ArmEncoder::new_thumb2();
7733        let op = ArmOp::And {
7734            rd: Reg::R0,
7735            rn: Reg::R1,
7736            op2: Operand2::Reg(Reg::R2),
7737        };
7738
7739        let code = encoder.encode(&op).unwrap();
7740        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7741    }
7742
7743    #[test]
7744    fn test_encode_lsl_thumb2_low_regs() {
7745        let encoder = ArmEncoder::new_thumb2();
7746        let op = ArmOp::Lsl {
7747            rd: Reg::R0,
7748            rn: Reg::R1,
7749            shift: 5,
7750        };
7751
7752        let code = encoder.encode(&op).unwrap();
7753        assert_eq!(code.len(), 2); // 16-bit for low registers
7754    }
7755
7756    #[test]
7757    fn test_encode_clz_thumb2() {
7758        let encoder = ArmEncoder::new_thumb2();
7759        let op = ArmOp::Clz {
7760            rd: Reg::R0,
7761            rm: Reg::R1,
7762        };
7763
7764        let code = encoder.encode(&op).unwrap();
7765        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
7766    }
7767
7768    #[test]
7769    fn test_encode_bx_thumb2() {
7770        let encoder = ArmEncoder::new_thumb2();
7771        let op = ArmOp::Bx { rm: Reg::LR };
7772
7773        let code = encoder.encode(&op).unwrap();
7774        assert_eq!(code.len(), 2); // 16-bit instruction
7775
7776        // BX LR: 0x4770
7777        assert_eq!(code, vec![0x70, 0x47]);
7778    }
7779
7780    // ========================================================================
7781    // f32 pseudo-op encoding tests
7782    // ========================================================================
7783
7784    #[test]
7785    fn test_encode_f32_abs_arm32() {
7786        let encoder = ArmEncoder::new_arm32();
7787        let op = ArmOp::F32Abs {
7788            sd: VfpReg::S0,
7789            sm: VfpReg::S2,
7790        };
7791        let code = encoder.encode(&op).unwrap();
7792        assert_eq!(code.len(), 4); // Single VFP instruction
7793    }
7794
7795    #[test]
7796    fn test_encode_f32_neg_arm32() {
7797        let encoder = ArmEncoder::new_arm32();
7798        let op = ArmOp::F32Neg {
7799            sd: VfpReg::S0,
7800            sm: VfpReg::S2,
7801        };
7802        let code = encoder.encode(&op).unwrap();
7803        assert_eq!(code.len(), 4);
7804    }
7805
7806    #[test]
7807    fn test_encode_f32_sqrt_arm32() {
7808        let encoder = ArmEncoder::new_arm32();
7809        let op = ArmOp::F32Sqrt {
7810            sd: VfpReg::S0,
7811            sm: VfpReg::S2,
7812        };
7813        let code = encoder.encode(&op).unwrap();
7814        assert_eq!(code.len(), 4);
7815    }
7816
7817    #[test]
7818    fn test_encode_f32_ceil_arm32() {
7819        let encoder = ArmEncoder::new_arm32();
7820        let op = ArmOp::F32Ceil {
7821            sd: VfpReg::S0,
7822            sm: VfpReg::S2,
7823        };
7824        let code = encoder.encode(&op).unwrap();
7825        // VMRS + BIC + ORR + VMSR + VCVT.S32.F32 + VMRS + BIC + VMSR + VCVT.F32.S32
7826        assert_eq!(code.len(), 36);
7827    }
7828
7829    #[test]
7830    fn test_encode_f32_floor_thumb2() {
7831        let encoder = ArmEncoder::new_thumb2();
7832        let op = ArmOp::F32Floor {
7833            sd: VfpReg::S0,
7834            sm: VfpReg::S2,
7835        };
7836        let code = encoder.encode(&op).unwrap();
7837        // VMRS + BIC.W + ORR.W + VMSR + VCVT + VMRS + BIC.W + VMSR + VCVT.F32.S32
7838        assert_eq!(code.len(), 36);
7839    }
7840
7841    #[test]
7842    fn test_encode_f32_min_arm32() {
7843        let encoder = ArmEncoder::new_arm32();
7844        let op = ArmOp::F32Min {
7845            sd: VfpReg::S0,
7846            sn: VfpReg::S2,
7847            sm: VfpReg::S4,
7848        };
7849        let code = encoder.encode(&op).unwrap();
7850        assert_eq!(code.len(), 16); // VMOV + VCMP + VMRS + conditional VMOV
7851    }
7852
7853    #[test]
7854    fn test_encode_f32_max_thumb2() {
7855        let encoder = ArmEncoder::new_thumb2();
7856        let op = ArmOp::F32Max {
7857            sd: VfpReg::S0,
7858            sn: VfpReg::S2,
7859            sm: VfpReg::S4,
7860        };
7861        let code = encoder.encode(&op).unwrap();
7862        // VMOV(4) + VCMP(4) + VMRS(4) + IT(2) + VMOV(4) = 18
7863        assert_eq!(code.len(), 18);
7864    }
7865
7866    #[test]
7867    fn test_encode_f32_copysign_arm32() {
7868        let encoder = ArmEncoder::new_arm32();
7869        let op = ArmOp::F32Copysign {
7870            sd: VfpReg::S0,
7871            sn: VfpReg::S2,
7872            sm: VfpReg::S4,
7873        };
7874        let code = encoder.encode(&op).unwrap();
7875        // VMOV + VMOV + AND + BIC + ORR + VMOV = 6 * 4 = 24
7876        assert_eq!(code.len(), 24);
7877    }
7878
7879    // ========================================================================
7880    // f64 encoding tests
7881    // ========================================================================
7882
7883    #[test]
7884    fn test_encode_f64_add_arm32() {
7885        let encoder = ArmEncoder::new_arm32();
7886        let op = ArmOp::F64Add {
7887            dd: VfpReg::D0,
7888            dn: VfpReg::D1,
7889            dm: VfpReg::D2,
7890        };
7891        let code = encoder.encode(&op).unwrap();
7892        assert_eq!(code.len(), 4);
7893        // VADD.F64 D0, D1, D2: check coprocessor is cp11 (0xB)
7894        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7895        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11
7896    }
7897
7898    #[test]
7899    fn test_encode_f64_sub_thumb2() {
7900        let encoder = ArmEncoder::new_thumb2();
7901        let op = ArmOp::F64Sub {
7902            dd: VfpReg::D0,
7903            dn: VfpReg::D1,
7904            dm: VfpReg::D2,
7905        };
7906        let code = encoder.encode(&op).unwrap();
7907        assert_eq!(code.len(), 4); // 32-bit VFP as two Thumb halfwords
7908    }
7909
7910    #[test]
7911    fn test_encode_f64_mul_arm32() {
7912        let encoder = ArmEncoder::new_arm32();
7913        let op = ArmOp::F64Mul {
7914            dd: VfpReg::D0,
7915            dn: VfpReg::D1,
7916            dm: VfpReg::D2,
7917        };
7918        let code = encoder.encode(&op).unwrap();
7919        assert_eq!(code.len(), 4);
7920    }
7921
7922    #[test]
7923    fn test_encode_f64_div_arm32() {
7924        let encoder = ArmEncoder::new_arm32();
7925        let op = ArmOp::F64Div {
7926            dd: VfpReg::D0,
7927            dn: VfpReg::D1,
7928            dm: VfpReg::D2,
7929        };
7930        let code = encoder.encode(&op).unwrap();
7931        assert_eq!(code.len(), 4);
7932    }
7933
7934    #[test]
7935    fn test_encode_f64_abs_arm32() {
7936        let encoder = ArmEncoder::new_arm32();
7937        let op = ArmOp::F64Abs {
7938            dd: VfpReg::D0,
7939            dm: VfpReg::D2,
7940        };
7941        let code = encoder.encode(&op).unwrap();
7942        assert_eq!(code.len(), 4);
7943    }
7944
7945    #[test]
7946    fn test_encode_f64_neg_arm32() {
7947        let encoder = ArmEncoder::new_arm32();
7948        let op = ArmOp::F64Neg {
7949            dd: VfpReg::D0,
7950            dm: VfpReg::D2,
7951        };
7952        let code = encoder.encode(&op).unwrap();
7953        assert_eq!(code.len(), 4);
7954    }
7955
7956    #[test]
7957    fn test_encode_f64_sqrt_arm32() {
7958        let encoder = ArmEncoder::new_arm32();
7959        let op = ArmOp::F64Sqrt {
7960            dd: VfpReg::D0,
7961            dm: VfpReg::D2,
7962        };
7963        let code = encoder.encode(&op).unwrap();
7964        assert_eq!(code.len(), 4);
7965    }
7966
7967    #[test]
7968    fn test_encode_f64_load_arm32() {
7969        let encoder = ArmEncoder::new_arm32();
7970        let op = ArmOp::F64Load {
7971            dd: VfpReg::D0,
7972            addr: MemAddr::imm(Reg::R0, 8),
7973        };
7974        let code = encoder.encode(&op).unwrap();
7975        assert_eq!(code.len(), 4);
7976        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7977        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11 for F64
7978        assert_eq!(instr & 0xFF, 2); // offset 8 / 4 = 2
7979    }
7980
7981    #[test]
7982    fn test_encode_f64_store_thumb2() {
7983        let encoder = ArmEncoder::new_thumb2();
7984        let op = ArmOp::F64Store {
7985            dd: VfpReg::D0,
7986            addr: MemAddr::imm(Reg::SP, 0),
7987        };
7988        let code = encoder.encode(&op).unwrap();
7989        assert_eq!(code.len(), 4);
7990    }
7991
7992    #[test]
7993    fn test_encode_f64_compare_arm32() {
7994        let encoder = ArmEncoder::new_arm32();
7995        let op = ArmOp::F64Eq {
7996            rd: Reg::R0,
7997            dn: VfpReg::D0,
7998            dm: VfpReg::D1,
7999        };
8000        let code = encoder.encode(&op).unwrap();
8001        assert_eq!(code.len(), 16); // VCMP + VMRS + MOV #0 + MOVcond #1
8002    }
8003
8004    #[test]
8005    fn test_encode_f64_compare_thumb2() {
8006        let encoder = ArmEncoder::new_thumb2();
8007        let op = ArmOp::F64Lt {
8008            rd: Reg::R0,
8009            dn: VfpReg::D0,
8010            dm: VfpReg::D1,
8011        };
8012        let code = encoder.encode(&op).unwrap();
8013        // VCMP(4) + VMRS(4) + MOVS(2) + IT(2) + MOV(2) = 14
8014        assert_eq!(code.len(), 14);
8015    }
8016
8017    #[test]
8018    fn test_encode_f64_const_arm32() {
8019        let encoder = ArmEncoder::new_arm32();
8020        let op = ArmOp::F64Const {
8021            dd: VfpReg::D0,
8022            value: 3.125,
8023        };
8024        let code = encoder.encode(&op).unwrap();
8025        // MOVW(4) + MOVT(4) + MOVW(4) + MOVT(4) + VMOV(4) = 20
8026        assert_eq!(code.len(), 20);
8027    }
8028
8029    #[test]
8030    fn test_encode_f64_const_thumb2() {
8031        let encoder = ArmEncoder::new_thumb2();
8032        let op = ArmOp::F64Const {
8033            dd: VfpReg::D0,
8034            value: 2.5,
8035        };
8036        let code = encoder.encode(&op).unwrap();
8037        // MOVW(4) + MOVT(4) + MOVW(4) + MOVT(4) + VMOV(4) = 20
8038        assert_eq!(code.len(), 20);
8039    }
8040
8041    #[test]
8042    fn test_encode_f64_convert_i32s_arm32() {
8043        let encoder = ArmEncoder::new_arm32();
8044        let op = ArmOp::F64ConvertI32S {
8045            dd: VfpReg::D0,
8046            rm: Reg::R0,
8047        };
8048        let code = encoder.encode(&op).unwrap();
8049        // VMOV(4) + VCVT(4) = 8
8050        assert_eq!(code.len(), 8);
8051    }
8052
8053    #[test]
8054    fn test_encode_f64_promote_f32_arm32() {
8055        let encoder = ArmEncoder::new_arm32();
8056        let op = ArmOp::F64PromoteF32 {
8057            dd: VfpReg::D0,
8058            sm: VfpReg::S0,
8059        };
8060        let code = encoder.encode(&op).unwrap();
8061        assert_eq!(code.len(), 4); // Single VCVT.F64.F32 instruction
8062    }
8063
8064    #[test]
8065    fn test_encode_f64_promote_f32_thumb2() {
8066        let encoder = ArmEncoder::new_thumb2();
8067        let op = ArmOp::F64PromoteF32 {
8068            dd: VfpReg::D0,
8069            sm: VfpReg::S0,
8070        };
8071        let code = encoder.encode(&op).unwrap();
8072        assert_eq!(code.len(), 4);
8073    }
8074
8075    #[test]
8076    fn test_encode_i32_trunc_f64s_arm32() {
8077        let encoder = ArmEncoder::new_arm32();
8078        let op = ArmOp::I32TruncF64S {
8079            rd: Reg::R0,
8080            dm: VfpReg::D0,
8081        };
8082        let code = encoder.encode(&op).unwrap();
8083        // VCVT(4) + VMOV(4) = 8
8084        assert_eq!(code.len(), 8);
8085    }
8086
8087    #[test]
8088    fn test_encode_f64_reinterpret_i64_arm32() {
8089        let encoder = ArmEncoder::new_arm32();
8090        let op = ArmOp::F64ReinterpretI64 {
8091            dd: VfpReg::D0,
8092            rmlo: Reg::R0,
8093            rmhi: Reg::R1,
8094        };
8095        let code = encoder.encode(&op).unwrap();
8096        assert_eq!(code.len(), 4); // Single VMOV instruction
8097    }
8098
8099    #[test]
8100    fn test_encode_i64_reinterpret_f64_thumb2() {
8101        let encoder = ArmEncoder::new_thumb2();
8102        let op = ArmOp::I64ReinterpretF64 {
8103            rdlo: Reg::R0,
8104            rdhi: Reg::R1,
8105            dm: VfpReg::D0,
8106        };
8107        let code = encoder.encode(&op).unwrap();
8108        assert_eq!(code.len(), 4);
8109    }
8110
8111    #[test]
8112    fn test_encode_f64_trunc_thumb2() {
8113        let encoder = ArmEncoder::new_thumb2();
8114        let op = ArmOp::F64Trunc {
8115            dd: VfpReg::D0,
8116            dm: VfpReg::D1,
8117        };
8118        let code = encoder.encode(&op).unwrap();
8119        // Two VFP instructions via Thumb encoding
8120        assert_eq!(code.len(), 8);
8121    }
8122
8123    #[test]
8124    fn test_encode_f64_min_arm32() {
8125        let encoder = ArmEncoder::new_arm32();
8126        let op = ArmOp::F64Min {
8127            dd: VfpReg::D0,
8128            dn: VfpReg::D1,
8129            dm: VfpReg::D2,
8130        };
8131        let code = encoder.encode(&op).unwrap();
8132        // VMOV + VCMP + VMRS + conditional VMOV = 16
8133        assert_eq!(code.len(), 16);
8134    }
8135
8136    #[test]
8137    fn test_f64_cp11_encoding() {
8138        // Verify that F64 instructions use coprocessor 11 (0xB), not 10 (0xA)
8139        let encoder = ArmEncoder::new_arm32();
8140
8141        // F64Add
8142        let code = encoder
8143            .encode(&ArmOp::F64Add {
8144                dd: VfpReg::D0,
8145                dn: VfpReg::D0,
8146                dm: VfpReg::D0,
8147            })
8148            .unwrap();
8149        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8150        assert_eq!((instr >> 8) & 0xF, 0xB, "F64 should use cp11");
8151
8152        // F32Add for comparison
8153        let code = encoder
8154            .encode(&ArmOp::F32Add {
8155                sd: VfpReg::S0,
8156                sn: VfpReg::S0,
8157                sm: VfpReg::S0,
8158            })
8159            .unwrap();
8160        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8161        assert_eq!((instr >> 8) & 0xF, 0xA, "F32 should use cp10");
8162    }
8163
8164    #[test]
8165    fn test_dreg_encoding_higher_registers() {
8166        let encoder = ArmEncoder::new_arm32();
8167
8168        // Test with D15 (highest register)
8169        let op = ArmOp::F64Add {
8170            dd: VfpReg::D15,
8171            dn: VfpReg::D14,
8172            dm: VfpReg::D13,
8173        };
8174        let code = encoder.encode(&op).unwrap();
8175        assert_eq!(code.len(), 4);
8176
8177        // Verify the register encoding worked (instruction is valid)
8178        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8179        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11
8180    }
8181
8182    // ========================================================================
8183    // Control flow encoding tests
8184    // ========================================================================
8185
8186    #[test]
8187    fn test_encode_label_emits_no_bytes() {
8188        let encoder = ArmEncoder::new_thumb2();
8189        let op = ArmOp::Label {
8190            name: ".Lblock_end_0".to_string(),
8191        };
8192        let code = encoder.encode(&op).unwrap();
8193        assert!(code.is_empty(), "Label should emit zero bytes");
8194
8195        let encoder32 = ArmEncoder::new_arm32();
8196        let code32 = encoder32.encode(&op).unwrap();
8197        assert!(
8198            code32.is_empty(),
8199            "Label should emit zero bytes in ARM32 too"
8200        );
8201    }
8202
8203    #[test]
8204    fn test_encode_bcc_eq_thumb2() {
8205        use synth_synthesis::Condition;
8206        let encoder = ArmEncoder::new_thumb2();
8207        let op = ArmOp::Bcc {
8208            cond: Condition::EQ,
8209            label: "target".to_string(),
8210        };
8211        let code = encoder.encode(&op).unwrap();
8212        assert_eq!(code.len(), 2); // 16-bit conditional branch
8213
8214        // BEQ with offset 0: 0xD000 in little-endian
8215        assert_eq!(code, vec![0x00, 0xD0]);
8216    }
8217
8218    #[test]
8219    fn test_encode_bcc_ne_thumb2() {
8220        use synth_synthesis::Condition;
8221        let encoder = ArmEncoder::new_thumb2();
8222        let op = ArmOp::Bcc {
8223            cond: Condition::NE,
8224            label: "target".to_string(),
8225        };
8226        let code = encoder.encode(&op).unwrap();
8227        assert_eq!(code.len(), 2);
8228
8229        // BNE with offset 0: 0xD100 in little-endian
8230        assert_eq!(code, vec![0x00, 0xD1]);
8231    }
8232
8233    #[test]
8234    fn test_encode_bcc_arm32() {
8235        use synth_synthesis::Condition;
8236        let encoder = ArmEncoder::new_arm32();
8237        let op = ArmOp::Bcc {
8238            cond: Condition::EQ,
8239            label: "target".to_string(),
8240        };
8241        let code = encoder.encode(&op).unwrap();
8242        assert_eq!(code.len(), 4); // 32-bit ARM instruction
8243
8244        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8245        // BEQ: cond=0x0, opcode=0xA, offset=0
8246        assert_eq!(instr & 0xF0000000, 0x00000000); // EQ condition
8247        assert_eq!(instr & 0x0F000000, 0x0A000000); // Branch opcode
8248    }
8249
8250    #[test]
8251    fn test_encode_udf_thumb2() {
8252        let encoder = ArmEncoder::new_thumb2();
8253        let op = ArmOp::Udf { imm: 0 };
8254        let code = encoder.encode(&op).unwrap();
8255        assert_eq!(code.len(), 2); // 16-bit
8256
8257        // UDF #0: 0xDE00 in little-endian
8258        assert_eq!(code, vec![0x00, 0xDE]);
8259    }
8260
8261    #[test]
8262    fn test_encode_nop_thumb2() {
8263        let encoder = ArmEncoder::new_thumb2();
8264        let op = ArmOp::Nop;
8265        let code = encoder.encode(&op).unwrap();
8266        assert_eq!(code.len(), 2); // 16-bit
8267
8268        // NOP: 0xBF00 in little-endian
8269        assert_eq!(code, vec![0x00, 0xBF]);
8270    }
8271
8272    // =========================================================================
8273    // i64 Thumb-2 encoding tests
8274    // =========================================================================
8275
8276    #[test]
8277    fn test_encode_i64_add_thumb2() {
8278        let encoder = ArmEncoder::new_thumb2();
8279        let op = ArmOp::I64Add {
8280            rdlo: Reg::R0,
8281            rdhi: Reg::R1,
8282            rnlo: Reg::R0,
8283            rnhi: Reg::R1,
8284            rmlo: Reg::R2,
8285            rmhi: Reg::R3,
8286        };
8287        let code = encoder.encode(&op).unwrap();
8288        // Should emit ADDS (2 bytes) + ADC.W (4 bytes) = 6 bytes
8289        assert_eq!(code.len(), 6, "I64Add should be 6 bytes (ADDS + ADC.W)");
8290    }
8291
8292    #[test]
8293    fn test_encode_i64_sub_thumb2() {
8294        let encoder = ArmEncoder::new_thumb2();
8295        let op = ArmOp::I64Sub {
8296            rdlo: Reg::R0,
8297            rdhi: Reg::R1,
8298            rnlo: Reg::R0,
8299            rnhi: Reg::R1,
8300            rmlo: Reg::R2,
8301            rmhi: Reg::R3,
8302        };
8303        let code = encoder.encode(&op).unwrap();
8304        // Should emit SUBS (2 bytes) + SBC.W (4 bytes) = 6 bytes
8305        assert_eq!(code.len(), 6, "I64Sub should be 6 bytes (SUBS + SBC.W)");
8306    }
8307
8308    #[test]
8309    fn test_encode_i64_and_thumb2() {
8310        let encoder = ArmEncoder::new_thumb2();
8311        let op = ArmOp::I64And {
8312            rdlo: Reg::R0,
8313            rdhi: Reg::R1,
8314            rnlo: Reg::R0,
8315            rnhi: Reg::R1,
8316            rmlo: Reg::R2,
8317            rmhi: Reg::R3,
8318        };
8319        let code = encoder.encode(&op).unwrap();
8320        // AND.W (4 bytes) + AND.W (4 bytes) = 8 bytes
8321        assert!(code.len() >= 4, "I64And should emit at least 4 bytes");
8322    }
8323
8324    #[test]
8325    fn test_encode_i64_or_thumb2() {
8326        let encoder = ArmEncoder::new_thumb2();
8327        let op = ArmOp::I64Or {
8328            rdlo: Reg::R0,
8329            rdhi: Reg::R1,
8330            rnlo: Reg::R0,
8331            rnhi: Reg::R1,
8332            rmlo: Reg::R2,
8333            rmhi: Reg::R3,
8334        };
8335        let code = encoder.encode(&op).unwrap();
8336        assert!(code.len() >= 4, "I64Or should emit at least 4 bytes");
8337    }
8338
8339    #[test]
8340    fn test_encode_i64_xor_thumb2() {
8341        let encoder = ArmEncoder::new_thumb2();
8342        let op = ArmOp::I64Xor {
8343            rdlo: Reg::R0,
8344            rdhi: Reg::R1,
8345            rnlo: Reg::R0,
8346            rnhi: Reg::R1,
8347            rmlo: Reg::R2,
8348            rmhi: Reg::R3,
8349        };
8350        let code = encoder.encode(&op).unwrap();
8351        assert!(code.len() >= 4, "I64Xor should emit at least 4 bytes");
8352    }
8353
8354    #[test]
8355    fn test_encode_i64_const_small_thumb2() {
8356        let encoder = ArmEncoder::new_thumb2();
8357        // Small constant: only needs MOVW for each half
8358        let op = ArmOp::I64Const {
8359            rdlo: Reg::R0,
8360            rdhi: Reg::R1,
8361            value: 42,
8362        };
8363        let code = encoder.encode(&op).unwrap();
8364        // MOVW R0, #42 (4 bytes) + MOVW R1, #0 (4 bytes) = 8 bytes minimum
8365        assert!(code.len() >= 8, "I64Const should emit at least 8 bytes");
8366    }
8367
8368    #[test]
8369    fn test_encode_i64_const_large_thumb2() {
8370        let encoder = ArmEncoder::new_thumb2();
8371        // Large constant: needs MOVW+MOVT for each half
8372        let op = ArmOp::I64Const {
8373            rdlo: Reg::R0,
8374            rdhi: Reg::R1,
8375            value: 0x1234_5678_9ABC_DEF0_u64 as i64,
8376        };
8377        let code = encoder.encode(&op).unwrap();
8378        // MOVW + MOVT for lo (8 bytes) + MOVW + MOVT for hi (8 bytes) = 16 bytes
8379        assert_eq!(
8380            code.len(),
8381            16,
8382            "I64Const with large value should be 16 bytes"
8383        );
8384    }
8385
8386    #[test]
8387    fn test_encode_i64_extend_i32_s_thumb2() {
8388        let encoder = ArmEncoder::new_thumb2();
8389        let op = ArmOp::I64ExtendI32S {
8390            rdlo: Reg::R0,
8391            rdhi: Reg::R1,
8392            rn: Reg::R0,
8393        };
8394        let code = encoder.encode(&op).unwrap();
8395        // When rdlo == rn, only ASR (4 bytes) is emitted
8396        assert_eq!(
8397            code.len(),
8398            4,
8399            "I64ExtendI32S (same reg) should be 4 bytes (ASR only)"
8400        );
8401    }
8402
8403    #[test]
8404    fn test_encode_i64_extend_i32_s_diff_reg_thumb2() {
8405        let encoder = ArmEncoder::new_thumb2();
8406        let op = ArmOp::I64ExtendI32S {
8407            rdlo: Reg::R0,
8408            rdhi: Reg::R1,
8409            rn: Reg::R2,
8410        };
8411        let code = encoder.encode(&op).unwrap();
8412        // MOV rdlo, rn (2 bytes for low regs) + ASR rdhi, rdlo, #31 (4 bytes) = 6 bytes
8413        assert!(
8414            code.len() >= 6,
8415            "I64ExtendI32S (diff reg) should be at least 6 bytes"
8416        );
8417    }
8418
8419    #[test]
8420    fn test_encode_i64_extend_i32_u_thumb2() {
8421        let encoder = ArmEncoder::new_thumb2();
8422        let op = ArmOp::I64ExtendI32U {
8423            rdlo: Reg::R0,
8424            rdhi: Reg::R1,
8425            rn: Reg::R0,
8426        };
8427        let code = encoder.encode(&op).unwrap();
8428        // When rdlo == rn, only MOV rdhi, #0 (2 bytes) is emitted
8429        assert_eq!(
8430            code.len(),
8431            2,
8432            "I64ExtendI32U (same reg) should be 2 bytes (MOV #0 only)"
8433        );
8434    }
8435
8436    #[test]
8437    fn test_encode_i32_wrap_i64_nop_thumb2() {
8438        let encoder = ArmEncoder::new_thumb2();
8439        // When rd == rnlo, should be a NOP
8440        let op = ArmOp::I32WrapI64 {
8441            rd: Reg::R0,
8442            rnlo: Reg::R0,
8443        };
8444        let code = encoder.encode(&op).unwrap();
8445        assert_eq!(code.len(), 2, "I32WrapI64 same reg should be NOP (2 bytes)");
8446        assert_eq!(code, vec![0x00, 0xBF]); // NOP
8447    }
8448
8449    #[test]
8450    fn test_encode_i32_wrap_i64_diff_reg_thumb2() {
8451        let encoder = ArmEncoder::new_thumb2();
8452        let op = ArmOp::I32WrapI64 {
8453            rd: Reg::R2,
8454            rnlo: Reg::R0,
8455        };
8456        let code = encoder.encode(&op).unwrap();
8457        // MOV R2, R0 (2 or 4 bytes)
8458        assert!(
8459            code.len() >= 2,
8460            "I32WrapI64 diff reg should emit at least 2 bytes"
8461        );
8462    }
8463
8464    #[test]
8465    fn test_encode_i64_eqz_thumb2() {
8466        let encoder = ArmEncoder::new_thumb2();
8467        let op = ArmOp::I64Eqz {
8468            rd: Reg::R0,
8469            rnlo: Reg::R0,
8470            rnhi: Reg::R1,
8471        };
8472        let code = encoder.encode(&op).unwrap();
8473        // Delegates to I64SetCondZ which is already encoded
8474        assert!(
8475            code.len() >= 6,
8476            "I64Eqz should emit at least 6 bytes for ORR+ITE+MOV+MOV"
8477        );
8478    }
8479
8480    #[test]
8481    fn test_encode_i64_eq_thumb2() {
8482        let encoder = ArmEncoder::new_thumb2();
8483        let op = ArmOp::I64Eq {
8484            rd: Reg::R0,
8485            rnlo: Reg::R0,
8486            rnhi: Reg::R1,
8487            rmlo: Reg::R2,
8488            rmhi: Reg::R3,
8489        };
8490        let code = encoder.encode(&op).unwrap();
8491        // Delegates to I64SetCond EQ: CMP lo + IT EQ + CMPEQ hi + ITE EQ + MOV 1 + MOV 0
8492        assert!(code.len() >= 10, "I64Eq should emit at least 10 bytes");
8493    }
8494
8495    #[test]
8496    fn test_encode_i64_ldr_thumb2() {
8497        let encoder = ArmEncoder::new_thumb2();
8498        let op = ArmOp::I64Ldr {
8499            rdlo: Reg::R0,
8500            rdhi: Reg::R1,
8501            addr: MemAddr::imm(Reg::SP, 0),
8502        };
8503        let code = encoder.encode(&op).unwrap();
8504        // Two LDR instructions (lo at offset, hi at offset+4)
8505        assert!(code.len() >= 4, "I64Ldr should emit at least 4 bytes");
8506    }
8507
8508    #[test]
8509    fn test_encode_i64_str_thumb2() {
8510        let encoder = ArmEncoder::new_thumb2();
8511        let op = ArmOp::I64Str {
8512            rdlo: Reg::R0,
8513            rdhi: Reg::R1,
8514            addr: MemAddr::imm(Reg::SP, 0),
8515        };
8516        let code = encoder.encode(&op).unwrap();
8517        // Two STR instructions (lo at offset, hi at offset+4)
8518        assert!(code.len() >= 4, "I64Str should emit at least 4 bytes");
8519    }
8520
8521    #[test]
8522    fn test_encode_i64_all_comparisons_thumb2() {
8523        let encoder = ArmEncoder::new_thumb2();
8524
8525        let ops = vec![
8526            ArmOp::I64Ne {
8527                rd: Reg::R0,
8528                rnlo: Reg::R0,
8529                rnhi: Reg::R1,
8530                rmlo: Reg::R2,
8531                rmhi: Reg::R3,
8532            },
8533            ArmOp::I64LtS {
8534                rd: Reg::R0,
8535                rnlo: Reg::R0,
8536                rnhi: Reg::R1,
8537                rmlo: Reg::R2,
8538                rmhi: Reg::R3,
8539            },
8540            ArmOp::I64LtU {
8541                rd: Reg::R0,
8542                rnlo: Reg::R0,
8543                rnhi: Reg::R1,
8544                rmlo: Reg::R2,
8545                rmhi: Reg::R3,
8546            },
8547            ArmOp::I64LeS {
8548                rd: Reg::R0,
8549                rnlo: Reg::R0,
8550                rnhi: Reg::R1,
8551                rmlo: Reg::R2,
8552                rmhi: Reg::R3,
8553            },
8554            ArmOp::I64LeU {
8555                rd: Reg::R0,
8556                rnlo: Reg::R0,
8557                rnhi: Reg::R1,
8558                rmlo: Reg::R2,
8559                rmhi: Reg::R3,
8560            },
8561            ArmOp::I64GtS {
8562                rd: Reg::R0,
8563                rnlo: Reg::R0,
8564                rnhi: Reg::R1,
8565                rmlo: Reg::R2,
8566                rmhi: Reg::R3,
8567            },
8568            ArmOp::I64GtU {
8569                rd: Reg::R0,
8570                rnlo: Reg::R0,
8571                rnhi: Reg::R1,
8572                rmlo: Reg::R2,
8573                rmhi: Reg::R3,
8574            },
8575            ArmOp::I64GeS {
8576                rd: Reg::R0,
8577                rnlo: Reg::R0,
8578                rnhi: Reg::R1,
8579                rmlo: Reg::R2,
8580                rmhi: Reg::R3,
8581            },
8582            ArmOp::I64GeU {
8583                rd: Reg::R0,
8584                rnlo: Reg::R0,
8585                rnhi: Reg::R1,
8586                rmlo: Reg::R2,
8587                rmhi: Reg::R3,
8588            },
8589        ];
8590
8591        for op in &ops {
8592            let code = encoder.encode(op).unwrap();
8593            assert!(
8594                code.len() >= 8,
8595                "i64 comparison {:?} should emit at least 8 bytes, got {}",
8596                op,
8597                code.len()
8598            );
8599        }
8600    }
8601
8602    #[test]
8603    fn test_encode_i64_const_zero_thumb2() {
8604        let encoder = ArmEncoder::new_thumb2();
8605        let op = ArmOp::I64Const {
8606            rdlo: Reg::R0,
8607            rdhi: Reg::R1,
8608            value: 0,
8609        };
8610        let code = encoder.encode(&op).unwrap();
8611        // MOVW R0, #0 (4 bytes) + MOVW R1, #0 (4 bytes) = 8 bytes
8612        assert_eq!(code.len(), 8, "I64Const(0) should be 8 bytes");
8613    }
8614
8615    #[test]
8616    fn test_encode_i64_const_negative_one_thumb2() {
8617        let encoder = ArmEncoder::new_thumb2();
8618        let op = ArmOp::I64Const {
8619            rdlo: Reg::R0,
8620            rdhi: Reg::R1,
8621            value: -1, // 0xFFFF_FFFF_FFFF_FFFF
8622        };
8623        let code = encoder.encode(&op).unwrap();
8624        // MOVW + MOVT for lo (8 bytes) + MOVW + MOVT for hi (8 bytes) = 16 bytes
8625        assert_eq!(code.len(), 16, "I64Const(-1) should be 16 bytes");
8626    }
8627
8628    // =========================================================================
8629    // Sub-word load/store encoding tests
8630    // =========================================================================
8631
8632    #[test]
8633    fn test_encode_ldrb_arm32() {
8634        let encoder = ArmEncoder::new_arm32();
8635        let op = ArmOp::Ldrb {
8636            rd: Reg::R0,
8637            addr: MemAddr::imm(Reg::R1, 4),
8638        };
8639        let code = encoder.encode(&op).unwrap();
8640        assert_eq!(code.len(), 4, "ARM32 LDRB should be 4 bytes");
8641        // LDRB R0, [R1, #4] = 0xE5D10004
8642        let encoded = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8643        assert_eq!(encoded, 0xE5D10004, "Should encode LDRB R0, [R1, #4]");
8644    }
8645
8646    #[test]
8647    fn test_encode_strb_arm32() {
8648        let encoder = ArmEncoder::new_arm32();
8649        let op = ArmOp::Strb {
8650            rd: Reg::R0,
8651            addr: MemAddr::imm(Reg::R1, 0),
8652        };
8653        let code = encoder.encode(&op).unwrap();
8654        assert_eq!(code.len(), 4, "ARM32 STRB should be 4 bytes");
8655        // STRB R0, [R1, #0] = 0xE5C10000
8656        let encoded = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8657        assert_eq!(encoded, 0xE5C10000, "Should encode STRB R0, [R1, #0]");
8658    }
8659
8660    #[test]
8661    fn test_encode_ldrh_arm32() {
8662        let encoder = ArmEncoder::new_arm32();
8663        let op = ArmOp::Ldrh {
8664            rd: Reg::R0,
8665            addr: MemAddr::imm(Reg::R1, 2),
8666        };
8667        let code = encoder.encode(&op).unwrap();
8668        assert_eq!(code.len(), 4, "ARM32 LDRH should be 4 bytes");
8669    }
8670
8671    #[test]
8672    fn test_encode_strh_arm32() {
8673        let encoder = ArmEncoder::new_arm32();
8674        let op = ArmOp::Strh {
8675            rd: Reg::R0,
8676            addr: MemAddr::imm(Reg::R1, 0),
8677        };
8678        let code = encoder.encode(&op).unwrap();
8679        assert_eq!(code.len(), 4, "ARM32 STRH should be 4 bytes");
8680    }
8681
8682    #[test]
8683    fn test_encode_ldrsb_arm32() {
8684        let encoder = ArmEncoder::new_arm32();
8685        let op = ArmOp::Ldrsb {
8686            rd: Reg::R0,
8687            addr: MemAddr::imm(Reg::R1, 0),
8688        };
8689        let code = encoder.encode(&op).unwrap();
8690        assert_eq!(code.len(), 4, "ARM32 LDRSB should be 4 bytes");
8691    }
8692
8693    #[test]
8694    fn test_encode_ldrsh_arm32() {
8695        let encoder = ArmEncoder::new_arm32();
8696        let op = ArmOp::Ldrsh {
8697            rd: Reg::R0,
8698            addr: MemAddr::imm(Reg::R1, 0),
8699        };
8700        let code = encoder.encode(&op).unwrap();
8701        assert_eq!(code.len(), 4, "ARM32 LDRSH should be 4 bytes");
8702    }
8703
8704    #[test]
8705    fn test_encode_ldrb_thumb2_16bit() {
8706        let encoder = ArmEncoder::new_thumb2();
8707        let op = ArmOp::Ldrb {
8708            rd: Reg::R0,
8709            addr: MemAddr::imm(Reg::R1, 4),
8710        };
8711        let code = encoder.encode(&op).unwrap();
8712        // Low registers + small offset -> 16-bit encoding
8713        assert_eq!(
8714            code.len(),
8715            2,
8716            "Thumb-2 LDRB with small offset should be 16-bit"
8717        );
8718    }
8719
8720    #[test]
8721    fn test_encode_ldrb_thumb2_32bit() {
8722        let encoder = ArmEncoder::new_thumb2();
8723        let op = ArmOp::Ldrb {
8724            rd: Reg::R0,
8725            addr: MemAddr::imm(Reg::R1, 100), // offset > 31 needs 32-bit
8726        };
8727        let code = encoder.encode(&op).unwrap();
8728        assert_eq!(
8729            code.len(),
8730            4,
8731            "Thumb-2 LDRB with large offset should be 32-bit"
8732        );
8733    }
8734
8735    #[test]
8736    fn test_encode_strb_thumb2_16bit() {
8737        let encoder = ArmEncoder::new_thumb2();
8738        let op = ArmOp::Strb {
8739            rd: Reg::R0,
8740            addr: MemAddr::imm(Reg::R1, 10),
8741        };
8742        let code = encoder.encode(&op).unwrap();
8743        assert_eq!(
8744            code.len(),
8745            2,
8746            "Thumb-2 STRB with small offset should be 16-bit"
8747        );
8748    }
8749
8750    #[test]
8751    fn test_encode_ldrh_thumb2_16bit() {
8752        let encoder = ArmEncoder::new_thumb2();
8753        let op = ArmOp::Ldrh {
8754            rd: Reg::R0,
8755            addr: MemAddr::imm(Reg::R1, 4), // offset aligned to 2, <= 62
8756        };
8757        let code = encoder.encode(&op).unwrap();
8758        assert_eq!(
8759            code.len(),
8760            2,
8761            "Thumb-2 LDRH with small aligned offset should be 16-bit"
8762        );
8763    }
8764
8765    #[test]
8766    fn test_encode_strh_thumb2_16bit() {
8767        let encoder = ArmEncoder::new_thumb2();
8768        let op = ArmOp::Strh {
8769            rd: Reg::R0,
8770            addr: MemAddr::imm(Reg::R1, 4),
8771        };
8772        let code = encoder.encode(&op).unwrap();
8773        assert_eq!(
8774            code.len(),
8775            2,
8776            "Thumb-2 STRH with small aligned offset should be 16-bit"
8777        );
8778    }
8779
8780    #[test]
8781    fn test_encode_ldrsb_thumb2() {
8782        let encoder = ArmEncoder::new_thumb2();
8783        let op = ArmOp::Ldrsb {
8784            rd: Reg::R0,
8785            addr: MemAddr::imm(Reg::R1, 0),
8786        };
8787        let code = encoder.encode(&op).unwrap();
8788        // LDRSB has no 16-bit immediate form, always 32-bit
8789        assert_eq!(code.len(), 4, "Thumb-2 LDRSB should be 32-bit");
8790    }
8791
8792    #[test]
8793    fn test_encode_ldrsh_thumb2() {
8794        let encoder = ArmEncoder::new_thumb2();
8795        let op = ArmOp::Ldrsh {
8796            rd: Reg::R0,
8797            addr: MemAddr::imm(Reg::R1, 0),
8798        };
8799        let code = encoder.encode(&op).unwrap();
8800        assert_eq!(code.len(), 4, "Thumb-2 LDRSH should be 32-bit");
8801    }
8802
8803    #[test]
8804    fn test_encode_memory_size_thumb2() {
8805        let encoder = ArmEncoder::new_thumb2();
8806        let op = ArmOp::MemorySize { rd: Reg::R0 };
8807        let code = encoder.encode(&op).unwrap();
8808        // R0 and R10 are not both low registers, so this needs careful handling
8809        assert!(!code.is_empty(), "MemorySize should produce code");
8810    }
8811
8812    #[test]
8813    fn test_encode_memory_grow_thumb2() {
8814        let encoder = ArmEncoder::new_thumb2();
8815        let op = ArmOp::MemoryGrow {
8816            rd: Reg::R0,
8817            rn: Reg::R0,
8818        };
8819        let code = encoder.encode(&op).unwrap();
8820        assert_eq!(code.len(), 4, "MemoryGrow (MVN) should be 32-bit Thumb-2");
8821    }
8822
8823    #[test]
8824    fn test_encode_subword_reg_offset_thumb2() {
8825        let encoder = ArmEncoder::new_thumb2();
8826
8827        // LDRB with register offset
8828        let op = ArmOp::Ldrb {
8829            rd: Reg::R0,
8830            addr: MemAddr::reg(Reg::R1, Reg::R2),
8831        };
8832        let code = encoder.encode(&op).unwrap();
8833        assert_eq!(
8834            code.len(),
8835            4,
8836            "Thumb-2 LDRB with reg offset should be 32-bit"
8837        );
8838
8839        // STRB with register offset
8840        let op = ArmOp::Strb {
8841            rd: Reg::R0,
8842            addr: MemAddr::reg(Reg::R1, Reg::R2),
8843        };
8844        let code = encoder.encode(&op).unwrap();
8845        assert_eq!(
8846            code.len(),
8847            4,
8848            "Thumb-2 STRB with reg offset should be 32-bit"
8849        );
8850
8851        // LDRH with register offset
8852        let op = ArmOp::Ldrh {
8853            rd: Reg::R0,
8854            addr: MemAddr::reg(Reg::R1, Reg::R2),
8855        };
8856        let code = encoder.encode(&op).unwrap();
8857        assert_eq!(
8858            code.len(),
8859            4,
8860            "Thumb-2 LDRH with reg offset should be 32-bit"
8861        );
8862
8863        // STRH with register offset
8864        let op = ArmOp::Strh {
8865            rd: Reg::R0,
8866            addr: MemAddr::reg(Reg::R1, Reg::R2),
8867        };
8868        let code = encoder.encode(&op).unwrap();
8869        assert_eq!(
8870            code.len(),
8871            4,
8872            "Thumb-2 STRH with reg offset should be 32-bit"
8873        );
8874    }
8875
8876    #[test]
8877    fn test_encode_subword_reg_imm_offset_thumb2() {
8878        let encoder = ArmEncoder::new_thumb2();
8879
8880        // LDRB with both register and immediate offset
8881        let op = ArmOp::Ldrb {
8882            rd: Reg::R0,
8883            addr: MemAddr::reg_imm(Reg::R1, Reg::R2, 4),
8884        };
8885        let code = encoder.encode(&op).unwrap();
8886        // ADD R12, R2, #4 (4 bytes) + LDRB R0, [R1, R12] (4 bytes) = 8 bytes
8887        assert_eq!(
8888            code.len(),
8889            8,
8890            "Thumb-2 LDRB with reg+imm offset should be 8 bytes"
8891        );
8892    }
8893
8894    // ========================================================================
8895    // Helium MVE encoding tests
8896    // ========================================================================
8897
8898    #[test]
8899    fn test_encode_mve_addi32_thumb2() {
8900        let encoder = ArmEncoder::new_thumb2();
8901        let op = ArmOp::MveAddI {
8902            qd: QReg::Q0,
8903            qn: QReg::Q1,
8904            qm: QReg::Q2,
8905            size: MveSize::S32,
8906        };
8907        let code = encoder.encode(&op).unwrap();
8908        assert_eq!(
8909            code.len(),
8910            4,
8911            "MVE VADD.I32 should be 4 bytes (Thumb-2 32-bit)"
8912        );
8913    }
8914
8915    #[test]
8916    fn test_encode_mve_subi16_thumb2() {
8917        let encoder = ArmEncoder::new_thumb2();
8918        let op = ArmOp::MveSubI {
8919            qd: QReg::Q0,
8920            qn: QReg::Q1,
8921            qm: QReg::Q2,
8922            size: MveSize::S16,
8923        };
8924        let code = encoder.encode(&op).unwrap();
8925        assert_eq!(code.len(), 4, "MVE VSUB.I16 should be 4 bytes");
8926    }
8927
8928    #[test]
8929    fn test_encode_mve_muli8_thumb2() {
8930        let encoder = ArmEncoder::new_thumb2();
8931        let op = ArmOp::MveMulI {
8932            qd: QReg::Q0,
8933            qn: QReg::Q1,
8934            qm: QReg::Q2,
8935            size: MveSize::S8,
8936        };
8937        let code = encoder.encode(&op).unwrap();
8938        assert_eq!(code.len(), 4, "MVE VMUL.I8 should be 4 bytes");
8939    }
8940
8941    #[test]
8942    fn test_encode_mve_bitwise_thumb2() {
8943        let encoder = ArmEncoder::new_thumb2();
8944
8945        let ops = vec![
8946            ArmOp::MveAnd {
8947                qd: QReg::Q0,
8948                qn: QReg::Q1,
8949                qm: QReg::Q2,
8950            },
8951            ArmOp::MveOrr {
8952                qd: QReg::Q0,
8953                qn: QReg::Q1,
8954                qm: QReg::Q2,
8955            },
8956            ArmOp::MveEor {
8957                qd: QReg::Q0,
8958                qn: QReg::Q1,
8959                qm: QReg::Q2,
8960            },
8961            ArmOp::MveBic {
8962                qd: QReg::Q0,
8963                qn: QReg::Q1,
8964                qm: QReg::Q2,
8965            },
8966        ];
8967        for op in ops {
8968            let code = encoder.encode(&op).unwrap();
8969            assert_eq!(code.len(), 4, "MVE bitwise op should be 4 bytes");
8970        }
8971    }
8972
8973    #[test]
8974    fn test_encode_mve_mvn_thumb2() {
8975        let encoder = ArmEncoder::new_thumb2();
8976        let op = ArmOp::MveMvn {
8977            qd: QReg::Q0,
8978            qm: QReg::Q1,
8979        };
8980        let code = encoder.encode(&op).unwrap();
8981        assert_eq!(code.len(), 4, "MVE VMVN should be 4 bytes");
8982    }
8983
8984    #[test]
8985    fn test_encode_mve_load_store_thumb2() {
8986        let encoder = ArmEncoder::new_thumb2();
8987
8988        let load = ArmOp::MveLoad {
8989            qd: QReg::Q0,
8990            addr: MemAddr::imm(Reg::R0, 16),
8991        };
8992        let code = encoder.encode(&load).unwrap();
8993        assert_eq!(code.len(), 4, "MVE VLDRW.32 should be 4 bytes");
8994
8995        let store = ArmOp::MveStore {
8996            qd: QReg::Q1,
8997            addr: MemAddr::imm(Reg::R1, 0),
8998        };
8999        let code = encoder.encode(&store).unwrap();
9000        assert_eq!(code.len(), 4, "MVE VSTRW.32 should be 4 bytes");
9001    }
9002
9003    #[test]
9004    fn test_encode_mve_const_thumb2() {
9005        let encoder = ArmEncoder::new_thumb2();
9006        let op = ArmOp::MveConst {
9007            qd: QReg::Q0,
9008            bytes: [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0],
9009        };
9010        let code = encoder.encode(&op).unwrap();
9011        // Should be 4 words of (MOVW R12 + VMOV Sn) = 4 * (4+4) = 32 bytes min
9012        // Some words with hi16=0 skip MOVT, so length varies
9013        assert!(
9014            code.len() >= 24,
9015            "MVE const should produce multiple instructions"
9016        );
9017    }
9018
9019    #[test]
9020    fn test_encode_mve_dup_thumb2() {
9021        let encoder = ArmEncoder::new_thumb2();
9022        let op = ArmOp::MveDup {
9023            qd: QReg::Q0,
9024            rn: Reg::R0,
9025            size: MveSize::S32,
9026        };
9027        let code = encoder.encode(&op).unwrap();
9028        assert_eq!(code.len(), 4, "MVE VDUP.32 should be 4 bytes");
9029    }
9030
9031    #[test]
9032    fn test_encode_mve_extract_lane_thumb2() {
9033        let encoder = ArmEncoder::new_thumb2();
9034        let op = ArmOp::MveExtractLane {
9035            rd: Reg::R0,
9036            qn: QReg::Q1,
9037            lane: 2,
9038            size: MveSize::S32,
9039        };
9040        let code = encoder.encode(&op).unwrap();
9041        assert_eq!(code.len(), 4, "MVE extract lane should be 4 bytes");
9042    }
9043
9044    #[test]
9045    fn test_encode_mve_insert_lane_thumb2() {
9046        let encoder = ArmEncoder::new_thumb2();
9047        let op = ArmOp::MveInsertLane {
9048            qd: QReg::Q0,
9049            rn: Reg::R1,
9050            lane: 3,
9051            size: MveSize::S32,
9052        };
9053        let code = encoder.encode(&op).unwrap();
9054        assert_eq!(code.len(), 4, "MVE insert lane should be 4 bytes");
9055    }
9056
9057    #[test]
9058    fn test_encode_mve_addf32_thumb2() {
9059        let encoder = ArmEncoder::new_thumb2();
9060        let op = ArmOp::MveAddF32 {
9061            qd: QReg::Q0,
9062            qn: QReg::Q1,
9063            qm: QReg::Q2,
9064        };
9065        let code = encoder.encode(&op).unwrap();
9066        assert_eq!(code.len(), 4, "MVE VADD.F32 should be 4 bytes");
9067    }
9068
9069    #[test]
9070    fn test_encode_mve_divf32_thumb2() {
9071        let encoder = ArmEncoder::new_thumb2();
9072        let op = ArmOp::MveDivF32 {
9073            qd: QReg::Q0,
9074            qn: QReg::Q1,
9075            qm: QReg::Q2,
9076        };
9077        let code = encoder.encode(&op).unwrap();
9078        // Lane-wise: 4 x VDIV.F32 = 4 x 4 = 16 bytes
9079        assert_eq!(
9080            code.len(),
9081            16,
9082            "MVE VDIV.F32 (lane-wise) should be 16 bytes"
9083        );
9084    }
9085
9086    #[test]
9087    fn test_encode_mve_sqrtf32_thumb2() {
9088        let encoder = ArmEncoder::new_thumb2();
9089        let op = ArmOp::MveSqrtF32 {
9090            qd: QReg::Q0,
9091            qm: QReg::Q1,
9092        };
9093        let code = encoder.encode(&op).unwrap();
9094        // Lane-wise: 4 x VSQRT.F32 = 4 x 4 = 16 bytes
9095        assert_eq!(
9096            code.len(),
9097            16,
9098            "MVE VSQRT.F32 (lane-wise) should be 16 bytes"
9099        );
9100    }
9101
9102    #[test]
9103    fn test_encode_mve_negf32_thumb2() {
9104        let encoder = ArmEncoder::new_thumb2();
9105        let op = ArmOp::MveNegF32 {
9106            qd: QReg::Q0,
9107            qm: QReg::Q1,
9108        };
9109        let code = encoder.encode(&op).unwrap();
9110        assert_eq!(code.len(), 4, "MVE VNEG.F32 should be 4 bytes");
9111    }
9112
9113    #[test]
9114    fn test_encode_mve_absf32_thumb2() {
9115        let encoder = ArmEncoder::new_thumb2();
9116        let op = ArmOp::MveAbsF32 {
9117            qd: QReg::Q0,
9118            qm: QReg::Q1,
9119        };
9120        let code = encoder.encode(&op).unwrap();
9121        assert_eq!(code.len(), 4, "MVE VABS.F32 should be 4 bytes");
9122    }
9123
9124    /// VCR-RA-001 / immediate-folding precondition: pins the Thumb-2 `AND`
9125    /// immediate encoding for the byte range and documents its bound.
9126    ///
9127    /// The `And { Operand2::Imm }` encoder packs the low 12 bits straight into
9128    /// the `i:imm3:imm8` field WITHOUT applying ThumbExpandImm (the modified-
9129    /// immediate expansion). For `imm <= 0xFF` (e.g. gale's int8 clamps
9130    /// `#0x7e` / `#0x7f`) that is correct — `i:imm3 = 0000` means "imm8
9131    /// zero-extended". So `and r2, r0, #0x7e` encodes to the canonical
9132    /// `00 f0 7e 02`. For `imm >= 0x100` the field would need a true
9133    /// ThumbExpandImm pattern (rotation / replication), which is NOT
9134    /// implemented here — so **immediate folding must gate on `imm <= 0xFF`**
9135    /// until the encoder is hardened to ThumbExpandImm/Ok-or-Err (the
9136    /// "encoder must be Ok-or-Err, never silently wrong" principle, #180/#185).
9137    /// This bound covers the measured `flat_flight` waste (#209).
9138    #[test]
9139    fn and_immediate_encodes_correctly_in_byte_range_documents_fold_bound() {
9140        let encoder = ArmEncoder::new_thumb2();
9141        let op = ArmOp::And {
9142            rd: Reg::R2,
9143            rn: Reg::R0,
9144            op2: Operand2::Imm(0x7e),
9145        };
9146        let code = encoder.encode(&op).unwrap();
9147        assert_eq!(
9148            code,
9149            vec![0x00, 0xf0, 0x7e, 0x02],
9150            "and r2, r0, #0x7e must encode to the canonical AND.W T1 (imm8=0x7e)"
9151        );
9152    }
9153
9154    /// #255: the shared ThumbExpandImm reverse-encoder underpinning the
9155    /// data-processing immediate fix. Encodable modified immediates round-trip to
9156    /// the expected `i:imm3:imm8` field; a genuinely non-modified value is `None`
9157    /// (caller must materialize into a register). Note `1000 = 0xFA ror 30` *is*
9158    /// representable (field 0xF7A) — the old encoder mis-encoded it (raw 0x3E8);
9159    /// this encodes it correctly.
9160    #[test]
9161    fn try_thumb_expand_imm_encodes_modified_immediates() {
9162        assert_eq!(try_thumb_expand_imm(0x7e), Some(0x07e)); // zero-extended byte
9163        assert_eq!(try_thumb_expand_imm(0xff), Some(0x0ff));
9164        assert_eq!(try_thumb_expand_imm(0x0001_0001), Some(0x101)); // 0x00XY00XY
9165        assert_eq!(try_thumb_expand_imm(0xff00_ff00), Some(0x2ff)); // 0xXY00XY00
9166        assert_eq!(try_thumb_expand_imm(0xffff_ffff), Some(0x3ff)); // 0xXYXYXYXY
9167        assert_eq!(try_thumb_expand_imm(0x100), Some(0xf80)); // 0x80 ror 31
9168        assert_eq!(try_thumb_expand_imm(0x8000_0000), Some(0x400)); // 0x80 ror 8
9169        assert_eq!(try_thumb_expand_imm(1000), Some(0xf7a)); // 0xFA ror 30
9170        // Genuinely unrepresentable (bits too far apart for an 8-bit window).
9171        assert_eq!(try_thumb_expand_imm(0x101), None);
9172        assert_eq!(try_thumb_expand_imm(0x12345), None);
9173    }
9174
9175    /// #255: CMP/ADDS/SUBS encode any valid modified immediate correctly, and
9176    /// ERROR (not silently mis-encode) on a genuinely unrepresentable one,
9177    /// forcing the selector to materialize into a register — closing the
9178    /// silent-miscompile class of #251/#253.
9179    #[test]
9180    fn cmp_adds_subs_immediate_error_on_non_modified_imm() {
9181        let encoder = ArmEncoder::new_thumb2();
9182        // cmp r0, #0xff → valid → Ok; cmp r0, #1000 → valid (0xFA ror 30) → Ok.
9183        assert!(encoder.encode_thumb32_cmp_imm(&Reg::R0, 0xff).is_ok());
9184        assert!(encoder.encode_thumb32_cmp_imm(&Reg::R0, 1000).is_ok());
9185        // cmp r0, #0x101 → NOT a modified immediate → Err (materialize-reg).
9186        assert!(
9187            encoder.encode_thumb32_cmp_imm(&Reg::R0, 0x101).is_err(),
9188            "cmp #0x101 must error, not compare the wrong constant"
9189        );
9190        assert!(
9191            encoder
9192                .encode_thumb32_adds(&Reg::R0, &Reg::R0, 0x101)
9193                .is_err()
9194        );
9195        assert!(
9196            encoder
9197                .encode_thumb32_subs(&Reg::R0, &Reg::R0, 0x101)
9198                .is_err()
9199        );
9200        // ...but a valid modified immediate still encodes.
9201        assert!(
9202            encoder
9203                .encode_thumb32_adds(&Reg::R0, &Reg::R0, 0x80)
9204                .is_ok()
9205        );
9206    }
9207
9208    /// #257: MLA (multiply-accumulate) encodes as MLS without the bit-4 op flag.
9209    /// `mla r2, r3, r4, r8` (rd=r2, rn=r3, rm=r4, ra=r8) → Thumb-2 `03 fb 04 82`.
9210    #[test]
9211    fn mla_thumb2_encodes_correctly() {
9212        let encoder = ArmEncoder::new_thumb2();
9213        let code = encoder
9214            .encode(&ArmOp::Mla {
9215                rd: Reg::R2,
9216                rn: Reg::R3,
9217                rm: Reg::R4,
9218                ra: Reg::R8,
9219            })
9220            .unwrap();
9221        // hw1 = 0xFB03, hw2 = (8<<12)|(2<<8)|4 = 0x8204
9222        assert_eq!(code, vec![0x03, 0xfb, 0x04, 0x82]);
9223    }
9224
9225    /// #259: LDR/STR (and sub-word) immediate-offset encoders truncated
9226    /// `offset & 0xFFF`, silently targeting the wrong address for offset >= 4096.
9227    /// They now error (the selector must use register-offset addressing) — the
9228    /// load/store sibling of the #253/#255 class. Offsets <= 4095 still encode.
9229    #[test]
9230    fn ldst_imm12_offset_errors_when_out_of_range() {
9231        let encoder = ArmEncoder::new_thumb2();
9232        // offset 0xFFF (4095): valid → Ok; ldr r0, [r1, #4095].
9233        assert!(
9234            encoder
9235                .encode_thumb32_ldr(&Reg::R0, &Reg::R1, 0xFFF)
9236                .is_ok()
9237        );
9238        // offset 0x1000 (4096): out of imm12 range → Err (not & 0xFFF → #0).
9239        assert!(
9240            encoder
9241                .encode_thumb32_ldr(&Reg::R0, &Reg::R1, 0x1000)
9242                .is_err(),
9243            "ldr offset 4096 must error, not wrap to 0"
9244        );
9245        assert!(
9246            encoder
9247                .encode_thumb32_str(&Reg::R0, &Reg::R1, 0x1000)
9248                .is_err()
9249        );
9250        assert!(
9251            encoder
9252                .encode_thumb32_ldrb_imm(&Reg::R0, &Reg::R1, 5000)
9253                .is_err()
9254        );
9255        assert!(
9256            encoder
9257                .encode_thumb32_strh_imm(&Reg::R0, &Reg::R1, 5000)
9258                .is_err()
9259        );
9260    }
9261
9262    /// Latent miscompile fix: ADD/SUB with a >0xFF immediate (e.g.
9263    /// `add sp, sp, #frame` for a >=256-byte frame) used ADD.W (T3), whose
9264    /// `i:imm3:imm8` is a ThumbExpandImm modified immediate — so `#256` silently
9265    /// encoded as `#0` (stack corruption). Use ADDW/SUBW (T4), a PLAIN 12-bit
9266    /// immediate, for 0x100..=0xFFF; keep T3 for <=0xFF (bit-identical); error
9267    /// beyond 4095.
9268    #[test]
9269    fn add_sub_large_immediate_use_addw_subw_not_misencoded() {
9270        let encoder = ArmEncoder::new_thumb2();
9271        // add sp, sp, #256  →  ADDW (T4) SP, SP, #256  =  0d f2 00 1d
9272        assert_eq!(
9273            encoder
9274                .encode(&ArmOp::Add {
9275                    rd: Reg::SP,
9276                    rn: Reg::SP,
9277                    op2: Operand2::Imm(256),
9278                })
9279                .unwrap(),
9280            vec![0x0d, 0xf2, 0x00, 0x1d],
9281            "add sp,sp,#256 must be ADDW (plain imm12), not a mis-encoded ADD.W"
9282        );
9283        // sub sp, sp, #256  →  SUBW (T4) SP, SP, #256  =  ad f2 00 1d
9284        assert_eq!(
9285            encoder
9286                .encode(&ArmOp::Sub {
9287                    rd: Reg::SP,
9288                    rn: Reg::SP,
9289                    op2: Operand2::Imm(256),
9290                })
9291                .unwrap(),
9292            vec![0xad, 0xf2, 0x00, 0x1d],
9293        );
9294        // > 4095 has no single-instruction encoding → error, not silent wrong.
9295        assert!(
9296            encoder
9297                .encode(&ArmOp::Add {
9298                    rd: Reg::SP,
9299                    rn: Reg::SP,
9300                    op2: Operand2::Imm(5000),
9301                })
9302                .is_err(),
9303            "add #5000 must error (no single ADDW), not mis-encode"
9304        );
9305    }
9306
9307    /// VCR-RA-001: ORR/EOR with a small immediate must encode the real
9308    /// instruction (not a silent `0xBF00` NOP). Pins the byte range and the
9309    /// Ok-or-Err bound that makes future Or/Eor immediate folding safe.
9310    #[test]
9311    fn orr_eor_immediate_encode_in_byte_range_else_error() {
9312        let encoder = ArmEncoder::new_thumb2();
9313        // orr r2, r0, #0x7e  →  ORR.W T1, imm8=0x7e
9314        assert_eq!(
9315            encoder
9316                .encode(&ArmOp::Orr {
9317                    rd: Reg::R2,
9318                    rn: Reg::R0,
9319                    op2: Operand2::Imm(0x7e),
9320                })
9321                .unwrap(),
9322            vec![0x40, 0xf0, 0x7e, 0x02],
9323        );
9324        // eor r2, r0, #0x7e  →  EOR.W T1, imm8=0x7e
9325        assert_eq!(
9326            encoder
9327                .encode(&ArmOp::Eor {
9328                    rd: Reg::R2,
9329                    rn: Reg::R0,
9330                    op2: Operand2::Imm(0x7e),
9331                })
9332                .unwrap(),
9333            vec![0x80, 0xf0, 0x7e, 0x02],
9334        );
9335        // Out-of-range immediates error rather than silently mis-encode / NOP.
9336        assert!(
9337            encoder
9338                .encode(&ArmOp::Orr {
9339                    rd: Reg::R2,
9340                    rn: Reg::R0,
9341                    op2: Operand2::Imm(0x140),
9342                })
9343                .is_err(),
9344            "ORR #0x140 must error, not emit a NOP"
9345        );
9346    }
9347
9348    #[test]
9349    fn test_encode_mve_different_qregs() {
9350        let encoder = ArmEncoder::new_thumb2();
9351
9352        // Test that different Q-register numbers produce different encodings
9353        let op1 = ArmOp::MveAddI {
9354            qd: QReg::Q0,
9355            qn: QReg::Q0,
9356            qm: QReg::Q0,
9357            size: MveSize::S32,
9358        };
9359        let op2 = ArmOp::MveAddI {
9360            qd: QReg::Q3,
9361            qn: QReg::Q5,
9362            qm: QReg::Q7,
9363            size: MveSize::S32,
9364        };
9365        let code1 = encoder.encode(&op1).unwrap();
9366        let code2 = encoder.encode(&op2).unwrap();
9367        assert_ne!(
9368            code1, code2,
9369            "Different Q-registers should produce different encodings"
9370        );
9371    }
9372
9373    #[test]
9374    fn test_encode_mve_arm32_nop() {
9375        // MVE instructions on ARM32 encoder should produce NOP (only Thumb-2 supported)
9376        let encoder = ArmEncoder::new_arm32();
9377        let op = ArmOp::MveAddI {
9378            qd: QReg::Q0,
9379            qn: QReg::Q1,
9380            qm: QReg::Q2,
9381            size: MveSize::S32,
9382        };
9383        let code = encoder.encode(&op).unwrap();
9384        assert_eq!(code.len(), 4, "ARM32 MVE should be 4 bytes (NOP)");
9385        // NOP in ARM32 is 0xE1A00000 (MOV R0, R0)
9386        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
9387        assert_eq!(instr, 0xE1A00000, "ARM32 MVE should encode as NOP");
9388    }
9389}