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    /// #594: A32 expansion of `ArmOp::CallIndirect` — mirror of the Thumb-2
126    /// arm (same contract: R11 holds the function-pointer table base, entry
127    /// `i` is a 4-byte code address, R12 is the encoder-scratch register):
128    ///
129    /// ```text
130    /// MOV r12, idx, LSL #2   ; table byte offset
131    /// LDR r12, [r11, r12]    ; load function pointer
132    /// BLX r12                ; indirect call
133    /// ```
134    ///
135    /// Bounds and type-signature checks are not emitted — parity with the
136    /// Thumb-2 path (tracked separately, see #594's note).
137    fn encode_arm_call_indirect(table_index_reg: &Reg) -> Vec<u8> {
138        let idx = reg_to_bits(table_index_reg);
139        let mut bytes = Vec::with_capacity(12);
140        // MOV r12, idx, LSL #2 — data-processing MOV, register op2 with
141        // imm5=2/LSL: cond=E, opcode=1101, S=0, Rd=r12.
142        let mov: u32 = 0xE1A0C000 | (2 << 7) | idx;
143        bytes.extend_from_slice(&mov.to_le_bytes());
144        // LDR r12, [r11, r12] — register offset, P=1 U=1 B=0 W=0 L=1.
145        let ldr: u32 = 0xE79BC00C;
146        bytes.extend_from_slice(&ldr.to_le_bytes());
147        // BLX r12 — cond=E, 0001 0010 1111 1111 1111 0011, Rm=r12.
148        let blx: u32 = 0xE12FFF3C;
149        bytes.extend_from_slice(&blx.to_le_bytes());
150        bytes
151    }
152
153    fn encode_arm(&self, op: &ArmOp) -> Result<Vec<u8>> {
154        // #206: ARM32 register-offset loads/stores. `encode_mem_addr` only
155        // returns the 12-bit immediate, so the immediate-form arms below
156        // silently DROP `addr.offset_reg` — a runtime address index vanished,
157        // turning `ldr rd,[rn,rm,#off]` into `ldr rd,[rn,#off]` (the access went
158        // to the wrong address). Compute the effective base into IP and re-encode
159        // against `[ip, #off]`, which is uniform for word/byte/halfword/signed.
160        if let Some(bytes) = self.encode_arm_reg_offset_mem(op)? {
161            return Ok(bytes);
162        }
163        // #594: call_indirect was encoded as a literal NOP on the A32 path
164        // (`--target cortex-r5`) — the call never happened and the function
165        // silently returned garbage. Emit the same three-instruction expansion
166        // as the Thumb-2 path (R11 = function-pointer table base, R12 scratch):
167        //   MOV r12, idx, LSL #2 ; LDR r12, [r11, r12] ; BLX r12
168        if let ArmOp::CallIndirect {
169            table_index_reg, ..
170        } = op
171        {
172            return Ok(Self::encode_arm_call_indirect(table_index_reg));
173        }
174        let instr: u32 = match op {
175            // Data processing instructions
176            ArmOp::Add { rd, rn, op2 } => {
177                let rd_bits = reg_to_bits(rd);
178                let rn_bits = reg_to_bits(rn);
179                let (op2_bits, i_flag) = encode_operand2(op2)?;
180
181                // ADD encoding: cond(4) | 00 | I(1) | 0100 | S(1) | Rn(4) | Rd(4) | operand2(12)
182                0xE0800000 // condition=always(E), opcode=ADD(0100), S=0
183                    | (i_flag << 25)
184                    | (rn_bits << 16)
185                    | (rd_bits << 12)
186                    | op2_bits
187            }
188
189            ArmOp::Sub { rd, rn, op2 } => {
190                let rd_bits = reg_to_bits(rd);
191                let rn_bits = reg_to_bits(rn);
192                let (op2_bits, i_flag) = encode_operand2(op2)?;
193
194                // SUB encoding: opcode=0010
195                0xE0400000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
196            }
197
198            // i64 support: ADDS, ADC, SUBS, SBC for ARM32
199            ArmOp::Adds { rd, rn, op2 } => {
200                let rd_bits = reg_to_bits(rd);
201                let rn_bits = reg_to_bits(rn);
202                let (op2_bits, i_flag) = encode_operand2(op2)?;
203
204                // ADDS encoding: opcode=0100, S=1
205                0xE0900000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
206            }
207
208            ArmOp::Adc { rd, rn, op2 } => {
209                let rd_bits = reg_to_bits(rd);
210                let rn_bits = reg_to_bits(rn);
211                let (op2_bits, i_flag) = encode_operand2(op2)?;
212
213                // ADC encoding: opcode=0101
214                0xE0A00000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
215            }
216
217            ArmOp::Subs { rd, rn, op2 } => {
218                let rd_bits = reg_to_bits(rd);
219                let rn_bits = reg_to_bits(rn);
220                let (op2_bits, i_flag) = encode_operand2(op2)?;
221
222                // SUBS encoding: opcode=0010, S=1
223                0xE0500000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
224            }
225
226            ArmOp::Sbc { rd, rn, op2 } => {
227                let rd_bits = reg_to_bits(rd);
228                let rn_bits = reg_to_bits(rn);
229                let (op2_bits, i_flag) = encode_operand2(op2)?;
230
231                // SBC encoding: opcode=0110
232                0xE0C00000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
233            }
234
235            ArmOp::Mul { rd, rn, rm } => {
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
240                // MUL encoding: cond(4) | 000000 | A(1) | S(1) | Rd(4) | Rn(4) | Rs(4) | 1001 | Rm(4)
241                0xE0000090 | (rd_bits << 16) | (rn_bits << 8) | rm_bits
242            }
243
244            ArmOp::Umull { rdlo, rdhi, rn, rm } => {
245                let rdlo_bits = reg_to_bits(rdlo);
246                let rdhi_bits = reg_to_bits(rdhi);
247                let rn_bits = reg_to_bits(rn);
248                let rm_bits = reg_to_bits(rm);
249
250                // UMULL encoding: cond(4) | 0000 1000 | RdHi(4) | RdLo(4) | Rm(4) | 1001 | Rn(4)
251                0xE0800090 | (rdhi_bits << 16) | (rdlo_bits << 12) | (rm_bits << 8) | rn_bits
252            }
253
254            ArmOp::Sdiv { rd, rn, rm } => {
255                let rd_bits = reg_to_bits(rd);
256                let rn_bits = reg_to_bits(rn);
257                let rm_bits = reg_to_bits(rm);
258
259                // SDIV encoding: cond(4) | 01110001 | Rd(4) | 1111 | Rm(4) | 0001 | Rn(4)
260                // ARMv7-M and above
261                0xE710F010 | (rd_bits << 16) | (rm_bits << 8) | rn_bits
262            }
263
264            ArmOp::Udiv { rd, rn, rm } => {
265                let rd_bits = reg_to_bits(rd);
266                let rn_bits = reg_to_bits(rn);
267                let rm_bits = reg_to_bits(rm);
268
269                // UDIV encoding: cond(4) | 01110011 | Rd(4) | 1111 | Rm(4) | 0001 | Rn(4)
270                // ARMv7-M and above
271                0xE730F010 | (rd_bits << 16) | (rm_bits << 8) | rn_bits
272            }
273
274            ArmOp::Mls { rd, rn, rm, ra } => {
275                let rd_bits = reg_to_bits(rd);
276                let rn_bits = reg_to_bits(rn);
277                let rm_bits = reg_to_bits(rm);
278                let ra_bits = reg_to_bits(ra);
279
280                // MLS encoding: cond(4) | 00000110 | Rd(4) | Ra(4) | Rm(4) | 1001 | Rn(4)
281                // Rd = Ra - (Rn * Rm)
282                0xE0600090 | (rd_bits << 16) | (ra_bits << 12) | (rm_bits << 8) | rn_bits
283            }
284
285            ArmOp::Mla { rd, rn, rm, ra } => {
286                let rd_bits = reg_to_bits(rd);
287                let rn_bits = reg_to_bits(rn);
288                let rm_bits = reg_to_bits(rm);
289                let ra_bits = reg_to_bits(ra);
290
291                // MLA encoding: cond(4) | 0000001 S | Rd(4) | Ra(4) | Rm(4) | 1001 | Rn(4)
292                // Rd = Ra + (Rn * Rm). Base 0xE0200090 (S=0).
293                0xE0200090 | (rd_bits << 16) | (ra_bits << 12) | (rm_bits << 8) | rn_bits
294            }
295
296            ArmOp::And { rd, rn, op2 } => {
297                let rd_bits = reg_to_bits(rd);
298                let rn_bits = reg_to_bits(rn);
299                let (op2_bits, i_flag) = encode_operand2(op2)?;
300
301                // AND encoding: opcode=0000
302                0xE0000000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
303            }
304
305            ArmOp::Orr { rd, rn, op2 } => {
306                let rd_bits = reg_to_bits(rd);
307                let rn_bits = reg_to_bits(rn);
308                let (op2_bits, i_flag) = encode_operand2(op2)?;
309
310                // ORR encoding: opcode=1100
311                0xE1800000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
312            }
313
314            ArmOp::Eor { rd, rn, op2 } => {
315                let rd_bits = reg_to_bits(rd);
316                let rn_bits = reg_to_bits(rn);
317                let (op2_bits, i_flag) = encode_operand2(op2)?;
318
319                // EOR encoding: opcode=0001
320                0xE0200000 | (i_flag << 25) | (rn_bits << 16) | (rd_bits << 12) | op2_bits
321            }
322
323            // Shift instructions
324            ArmOp::Lsl { rd, rn, shift } => {
325                let rd_bits = reg_to_bits(rd);
326                let rn_bits = reg_to_bits(rn);
327                let shift_bits = *shift & 0x1F;
328
329                // LSL encoding: MOV with shift
330                0xE1A00000 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
331            }
332
333            ArmOp::Lsr { rd, rn, shift } => {
334                let rd_bits = reg_to_bits(rd);
335                let rn_bits = reg_to_bits(rn);
336                let shift_bits = *shift & 0x1F;
337
338                // LSR encoding
339                0xE1A00020 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
340            }
341
342            ArmOp::Asr { rd, rn, shift } => {
343                let rd_bits = reg_to_bits(rd);
344                let rn_bits = reg_to_bits(rn);
345                let shift_bits = *shift & 0x1F;
346
347                // ASR encoding
348                0xE1A00040 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
349            }
350
351            ArmOp::Ror { rd, rn, shift } => {
352                let rd_bits = reg_to_bits(rd);
353                let rn_bits = reg_to_bits(rn);
354                let shift_bits = *shift & 0x1F;
355
356                // ROR encoding: MOV with ROR shift
357                0xE1A00060 | (rd_bits << 12) | (shift_bits << 7) | rn_bits
358            }
359
360            // Register-based shifts (ARM32)
361            // LSL Rd, Rn, Rm: cond 0001101S 0000 Rd Rs 0001 Rn
362            ArmOp::LslReg { rd, rn, rm } => {
363                let rd_bits = reg_to_bits(rd);
364                let rn_bits = reg_to_bits(rn);
365                let rm_bits = reg_to_bits(rm);
366                0xE1A00010 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
367            }
368            ArmOp::LsrReg { rd, rn, rm } => {
369                let rd_bits = reg_to_bits(rd);
370                let rn_bits = reg_to_bits(rn);
371                let rm_bits = reg_to_bits(rm);
372                0xE1A00030 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
373            }
374            ArmOp::AsrReg { rd, rn, rm } => {
375                let rd_bits = reg_to_bits(rd);
376                let rn_bits = reg_to_bits(rn);
377                let rm_bits = reg_to_bits(rm);
378                0xE1A00050 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
379            }
380            ArmOp::RorReg { rd, rn, rm } => {
381                let rd_bits = reg_to_bits(rd);
382                let rn_bits = reg_to_bits(rn);
383                let rm_bits = reg_to_bits(rm);
384                0xE1A00070 | (rd_bits << 12) | (rm_bits << 8) | rn_bits
385            }
386
387            // RSB (Reverse Subtract): Rd = imm - Rn
388            ArmOp::Rsb { rd, rn, imm } => {
389                let rd_bits = reg_to_bits(rd);
390                let rn_bits = reg_to_bits(rn);
391                // RSB encoding: cond(4) | 00 1 0011 S | Rn(4) | Rd(4) | imm12
392                // Opcode for RSB = 0011, I=1 (immediate), S=0
393                0xE2600000 | (rn_bits << 16) | (rd_bits << 12) | (*imm & 0xFF)
394            }
395
396            // Bit manipulation instructions
397            ArmOp::Clz { rd, rm } => {
398                let rd_bits = reg_to_bits(rd);
399                let rm_bits = reg_to_bits(rm);
400
401                // CLZ encoding: cond(4) | 00010110 | 1111 | Rd(4) | 1111 | 0001 | Rm(4)
402                // ARMv5T and above
403                0xE16F0F10 | (rd_bits << 12) | rm_bits
404            }
405
406            ArmOp::Rbit { rd, rm } => {
407                let rd_bits = reg_to_bits(rd);
408                let rm_bits = reg_to_bits(rm);
409
410                // RBIT encoding: cond(4) | 01101111 | 1111 | Rd(4) | 1111 | 0011 | Rm(4)
411                // ARMv6T2 and above
412                0xE6FF0F30 | (rd_bits << 12) | rm_bits
413            }
414
415            ArmOp::Sxtb { rd, rm } => {
416                let rd_bits = reg_to_bits(rd);
417                let rm_bits = reg_to_bits(rm);
418
419                // SXTB encoding: cond(4) | 01101010 | 1111 | Rd(4) | rotate(2) | 00 | 0111 | Rm(4)
420                // ARMv6 and above. rotate=00 for no rotation
421                0xE6AF0070 | (rd_bits << 12) | rm_bits
422            }
423
424            ArmOp::Sxth { rd, rm } => {
425                let rd_bits = reg_to_bits(rd);
426                let rm_bits = reg_to_bits(rm);
427
428                // SXTH encoding: cond(4) | 01101011 | 1111 | Rd(4) | rotate(2) | 00 | 0111 | Rm(4)
429                // ARMv6 and above. rotate=00 for no rotation
430                0xE6BF0070 | (rd_bits << 12) | rm_bits
431            }
432
433            ArmOp::Uxtb { rd, rm } => {
434                let rd_bits = reg_to_bits(rd);
435                let rm_bits = reg_to_bits(rm);
436                // UXTB encoding: cond | 01101110 1111 Rd rotate 00 0111 Rm (rotate=00)
437                0xE6EF0070 | (rd_bits << 12) | rm_bits
438            }
439
440            ArmOp::Uxth { rd, rm } => {
441                let rd_bits = reg_to_bits(rd);
442                let rm_bits = reg_to_bits(rm);
443                // UXTH encoding: cond | 01101111 1111 Rd rotate 00 0111 Rm (rotate=00)
444                0xE6FF0070 | (rd_bits << 12) | rm_bits
445            }
446
447            // Move instructions
448            ArmOp::Mov { rd, op2 } => {
449                let rd_bits = reg_to_bits(rd);
450                let (op2_bits, i_flag) = encode_operand2(op2)?;
451
452                // MOV encoding: opcode=1101
453                0xE1A00000 | (i_flag << 25) | (rd_bits << 12) | op2_bits
454            }
455
456            ArmOp::Mvn { rd, op2 } => {
457                let rd_bits = reg_to_bits(rd);
458                let (op2_bits, i_flag) = encode_operand2(op2)?;
459
460                // MVN encoding: opcode=1111
461                0xE1E00000 | (i_flag << 25) | (rd_bits << 12) | op2_bits
462            }
463
464            // MOVW - Move Wide (ARM32)
465            // Encoding: cond(4) | 0011 0000 | imm4(4) | Rd(4) | imm12(12)
466            ArmOp::Movw { rd, imm16 } => {
467                let rd_bits = reg_to_bits(rd);
468                let imm4 = ((*imm16 as u32) >> 12) & 0xF;
469                let imm12 = (*imm16 as u32) & 0xFFF;
470                0xE3000000 | (imm4 << 16) | (rd_bits << 12) | imm12
471            }
472
473            // MOVT - Move Top (ARM32)
474            // Encoding: cond(4) | 0011 0100 | imm4(4) | Rd(4) | imm12(12)
475            ArmOp::Movt { rd, imm16 } => {
476                let rd_bits = reg_to_bits(rd);
477                let imm4 = ((*imm16 as u32) >> 12) & 0xF;
478                let imm12 = (*imm16 as u32) & 0xFFF;
479                0xE3400000 | (imm4 << 16) | (rd_bits << 12) | imm12
480            }
481
482            // #237: symbol-relative MOVW/MOVT (ARM mode) — addend in place, the
483            // backend records the MOVW_ABS/MOVT_ABS relocation against `symbol`.
484            ArmOp::MovwSym { rd, addend, .. } => {
485                let rd_bits = reg_to_bits(rd);
486                let v = (*addend as u32) & 0xffff;
487                0xE3000000 | (((v >> 12) & 0xF) << 16) | (rd_bits << 12) | (v & 0xFFF)
488            }
489            ArmOp::MovtSym { rd, addend, .. } => {
490                let rd_bits = reg_to_bits(rd);
491                let v = ((*addend as u32) >> 16) & 0xffff;
492                0xE3400000 | (((v >> 12) & 0xF) << 16) | (rd_bits << 12) | (v & 0xFFF)
493            }
494
495            // #345: LdrSym is the Thumb-2 literal-pool address load. A32 mode is
496            // not used for relocatable native-pointer objects; fail loudly rather
497            // than miscompile if it is ever reached here.
498            ArmOp::LdrSym { .. } => {
499                return Err(synth_core::Error::synthesis(
500                    "LdrSym (literal-pool address load) is Thumb-2-only",
501                ));
502            }
503
504            // Compare
505            ArmOp::Cmp { rn, op2 } => {
506                let rn_bits = reg_to_bits(rn);
507                let (op2_bits, i_flag) = encode_operand2(op2)?;
508
509                // CMP encoding: opcode=1010, S=1
510                0xE1500000 | (i_flag << 25) | (rn_bits << 16) | op2_bits
511            }
512
513            // Compare Negative (CMN) - computes Rn + op2 and sets flags
514            ArmOp::Cmn { rn, op2 } => {
515                let rn_bits = reg_to_bits(rn);
516                let (op2_bits, i_flag) = encode_operand2(op2)?;
517
518                // CMN encoding: opcode=1011, S=1
519                0xE1700000 | (i_flag << 25) | (rn_bits << 16) | op2_bits
520            }
521
522            // Load/Store
523            ArmOp::Ldr { rd, addr } => {
524                let rd_bits = reg_to_bits(rd);
525                let (base_bits, offset_bits) = encode_mem_addr(addr);
526
527                // LDR encoding: cond(4) | 01 | I(1) | P(1) | U(1) | B(1) | W(1) | L(1) | Rn(4) | Rd(4) | offset(12)
528                // P=1 (pre-indexed), U=1 (add offset), L=1 (load)
529                0xE5900000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
530            }
531
532            ArmOp::Str { rd, addr } => {
533                let rd_bits = reg_to_bits(rd);
534                let (base_bits, offset_bits) = encode_mem_addr(addr);
535
536                // STR encoding: L=0 (store)
537                0xE5800000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
538            }
539
540            // Sub-word loads (ARM32 encoding)
541            ArmOp::Ldrb { rd, addr } => {
542                let rd_bits = reg_to_bits(rd);
543                let (base_bits, offset_bits) = encode_mem_addr(addr);
544                // LDRB: LDR with B=1 (byte): cond|01|I|P|U|1|W|L|Rn|Rd|offset
545                0xE5D00000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
546            }
547
548            ArmOp::Ldrsb { rd, addr } => {
549                let rd_bits = reg_to_bits(rd);
550                let (base_bits, offset_bits) = encode_mem_addr(addr);
551                // LDRSB (misc load): cond|000|P|U|1|W|1|Rn|Rd|imm4H|1101|imm4L
552                // Simplified with immediate offset
553                let offset_val = offset_bits & 0xFF;
554                let imm4h = (offset_val >> 4) & 0xF;
555                let imm4l = offset_val & 0xF;
556                0xE1D000D0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
557            }
558
559            ArmOp::Ldrh { rd, addr } => {
560                let rd_bits = reg_to_bits(rd);
561                let (base_bits, offset_bits) = encode_mem_addr(addr);
562                // LDRH (misc load): cond|000|P|U|1|W|1|Rn|Rd|imm4H|1011|imm4L
563                let offset_val = offset_bits & 0xFF;
564                let imm4h = (offset_val >> 4) & 0xF;
565                let imm4l = offset_val & 0xF;
566                0xE1D000B0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
567            }
568
569            ArmOp::Ldrsh { rd, addr } => {
570                let rd_bits = reg_to_bits(rd);
571                let (base_bits, offset_bits) = encode_mem_addr(addr);
572                // LDRSH (misc load): cond|000|P|U|1|W|1|Rn|Rd|imm4H|1111|imm4L
573                let offset_val = offset_bits & 0xFF;
574                let imm4h = (offset_val >> 4) & 0xF;
575                let imm4l = offset_val & 0xF;
576                0xE1D000F0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
577            }
578
579            // Sub-word stores (ARM32 encoding)
580            ArmOp::Strb { rd, addr } => {
581                let rd_bits = reg_to_bits(rd);
582                let (base_bits, offset_bits) = encode_mem_addr(addr);
583                // STRB: STR with B=1 (byte): cond|01|I|P|U|1|W|0|Rn|Rd|offset
584                0xE5C00000 | (base_bits << 16) | (rd_bits << 12) | offset_bits
585            }
586
587            ArmOp::Strh { rd, addr } => {
588                let rd_bits = reg_to_bits(rd);
589                let (base_bits, offset_bits) = encode_mem_addr(addr);
590                // STRH (misc store): cond|000|P|U|1|W|0|Rn|Rd|imm4H|1011|imm4L
591                let offset_val = offset_bits & 0xFF;
592                let imm4h = (offset_val >> 4) & 0xF;
593                let imm4l = offset_val & 0xF;
594                0xE1C000B0 | (base_bits << 16) | (rd_bits << 12) | (imm4h << 8) | imm4l
595            }
596
597            // Memory management (ARM32 encoding)
598            ArmOp::MemorySize { rd } => {
599                let rd_bits = reg_to_bits(rd);
600                // MOV rd, R10, LSR #16  (memory size in bytes / 65536 = pages)
601                // cond|000|1101|S|0000|Rd|shift5|type|0|Rm
602                // LSR #16: shift5=10000, type=01
603                0xE1A00820 | (rd_bits << 12) | 0x0A // Rm=R10, shift=16, LSR
604            }
605
606            ArmOp::MemoryGrow { rd, .. } => {
607                let rd_bits = reg_to_bits(rd);
608                // On embedded, always fail: MOV rd, #-1
609                0xE3E00000 | (rd_bits << 12) // MVN rd, #0 = MOV rd, #-1
610            }
611
612            // Label pseudo-instruction: emits no machine code
613            ArmOp::Label { .. } => {
614                return Ok(Vec::new());
615            }
616
617            // Branch instructions
618            ArmOp::B { label: _ } => {
619                // B encoding: cond(4) | 1010 | offset(24)
620                // Simplified: branch to offset 0 (will be patched by linker/resolver)
621                0xEA000000
622            }
623
624            // Conditional branch to label (generic)
625            ArmOp::Bcc { cond, label: _ } => {
626                use synth_synthesis::Condition;
627                let cond_bits: u32 = match cond {
628                    Condition::EQ => 0x0,
629                    Condition::NE => 0x1,
630                    Condition::HS => 0x2,
631                    Condition::LO => 0x3,
632                    Condition::HI => 0x8,
633                    Condition::LS => 0x9,
634                    Condition::GE => 0xA,
635                    Condition::LT => 0xB,
636                    Condition::GT => 0xC,
637                    Condition::LE => 0xD,
638                };
639                // B<cond> with offset 0 (will be patched)
640                (cond_bits << 28) | 0x0A000000
641            }
642
643            // BHS (Branch if Higher or Same) - used for bounds checking
644            ArmOp::Bhs { label: _ } => {
645                // BHS encoding: cond(2=HS) | 1010 | offset(24)
646                0x2A000000 // BHS with offset 0
647            }
648
649            // BLO (Branch if Lower) - complementary to BHS
650            ArmOp::Blo { label: _ } => {
651                // BLO encoding: cond(3=LO) | 1010 | offset(24)
652                0x3A000000 // BLO with offset 0
653            }
654
655            // Branch with numeric offset (in instructions)
656            // ARM32 B instruction: offset is in instructions, stored as words
657            // The offset is relative to PC+8 (due to ARM pipeline)
658            ArmOp::BOffset { offset } => {
659                // B encoding: cond(4) | 1010 | offset(24)
660                // Offset is signed, in words (4-byte units)
661                // ARM adds PC+8 to the offset, so we need to adjust:
662                // target = PC + 8 + (offset * 4)
663                // For backward branch of N instructions: offset = -(N + 2)
664                // wrapping_sub keeps the encoder total under fuzzing (#186): an
665                // extreme i32::MIN offset would otherwise overflow-panic; for any
666                // real branch offset this is identical to `- 2`.
667                let adjusted_offset = offset.wrapping_sub(2); // Account for PC+8
668                let offset_bits = (adjusted_offset as u32) & 0x00FFFFFF;
669                0xEA000000 | offset_bits
670            }
671
672            // Conditional branch with numeric offset
673            ArmOp::BCondOffset { cond, offset } => {
674                use synth_synthesis::Condition;
675                let cond_bits: u32 = match cond {
676                    Condition::EQ => 0x0,
677                    Condition::NE => 0x1,
678                    Condition::HS => 0x2,
679                    Condition::LO => 0x3,
680                    Condition::HI => 0x8,
681                    Condition::LS => 0x9,
682                    Condition::GE => 0xA,
683                    Condition::LT => 0xB,
684                    Condition::GT => 0xC,
685                    Condition::LE => 0xD,
686                };
687                // B<cond> encoding: cond(4) | 1010 | offset(24)
688                // wrapping_sub: total under fuzzing (#186), identical for real offsets.
689                let adjusted_offset = offset.wrapping_sub(2); // Account for PC+8
690                let offset_bits = (adjusted_offset as u32) & 0x00FFFFFF;
691                (cond_bits << 28) | 0x0A000000 | offset_bits
692            }
693
694            ArmOp::Bl { label: _ } => {
695                // BL encoding: cond(4) | 1011 | offset(24)
696                0xEB000000
697            }
698
699            ArmOp::Bx { rm } => {
700                let rm_bits = reg_to_bits(rm);
701
702                // BX encoding: cond(4) | 000100101111111111110001 | Rm(4)
703                0xE12FFF10 | rm_bits
704            }
705
706            ArmOp::Blx { rm } => {
707                let rm_bits = reg_to_bits(rm);
708
709                // BLX (register) encoding: cond(4) | 000100101111111111110011 | Rm(4)
710                0xE12FFF30 | rm_bits
711            }
712
713            ArmOp::Push { regs } => {
714                // STMDB SP!, {regs} encoding: cond(4) | 100100 | 10 | 1101 | register_list(16)
715                let mut reg_list: u32 = 0;
716                for r in regs {
717                    reg_list |= 1 << reg_to_bits(r);
718                }
719                0xE92D0000 | reg_list
720            }
721
722            ArmOp::Pop { regs } => {
723                // LDMIA SP!, {regs} encoding: cond(4) | 100010 | 11 | 1101 | register_list(16)
724                let mut reg_list: u32 = 0;
725                for r in regs {
726                    reg_list |= 1 << reg_to_bits(r);
727                }
728                0xE8BD0000 | reg_list
729            }
730
731            ArmOp::Nop => {
732                // NOP encoding: MOV R0, R0
733                0xE1A00000
734            }
735
736            ArmOp::Udf { imm } => {
737                // UDF (Undefined) encoding in ARM: 0xE7F000F0 | (imm12_hi << 8) | imm4_lo
738                // We only use imm8, so split into imm4_hi and imm4_lo
739                let imm8 = *imm as u32;
740                0xE7F000F0 | ((imm8 & 0xF0) << 4) | (imm8 & 0x0F)
741            }
742
743            // Pseudo-instructions for verification - encode as NOP
744            // These are used in formal verification but not actual code generation
745            ArmOp::Popcnt { .. } => {
746                // Population count pseudo-instruction
747                // Not a real ARM instruction, would be expanded to actual code
748                0xE1A00000 // NOP for now
749            }
750
751            ArmOp::SetCond { .. } => {
752                // Condition evaluation pseudo-instruction
753                // Not a real ARM instruction, would be expanded to actual code
754                0xE1A00000 // NOP for now
755            }
756
757            ArmOp::SelectMove { .. } => {
758                // Conditional move pseudo-instruction for ARM32
759                // Would use MOV{cond} instruction
760                0xE1A00000 // NOP for now
761            }
762
763            ArmOp::Select { .. } => {
764                // Select pseudo-instruction
765                // Not a real ARM instruction, would be expanded to conditional moves
766                0xE1A00000 // NOP for now
767            }
768
769            ArmOp::LocalGet { .. } => {
770                // Local variable get pseudo-instruction
771                // Not a real ARM instruction, would be expanded to memory access
772                0xE1A00000 // NOP for now
773            }
774
775            ArmOp::LocalSet { .. } => {
776                // Local variable set pseudo-instruction
777                // Not a real ARM instruction, would be expanded to memory access
778                0xE1A00000 // NOP for now
779            }
780
781            ArmOp::LocalTee { .. } => {
782                // Local variable tee pseudo-instruction
783                // Not a real ARM instruction, would be expanded to memory access
784                0xE1A00000 // NOP for now
785            }
786
787            ArmOp::GlobalGet { .. } => {
788                // Global variable get pseudo-instruction
789                // Not a real ARM instruction, would be expanded to memory access
790                0xE1A00000 // NOP for now
791            }
792
793            ArmOp::GlobalSet { .. } => {
794                // Global variable set pseudo-instruction
795                // Not a real ARM instruction, would be expanded to memory access
796                0xE1A00000 // NOP for now
797            }
798
799            ArmOp::BrTable { .. } => {
800                // Branch table pseudo-instruction
801                // Not a real ARM instruction, would be expanded to jump table
802                0xE1A00000 // NOP for now
803            }
804
805            ArmOp::Call { .. } => {
806                // Function call pseudo-instruction
807                // Not a real ARM instruction, would be expanded to BL
808                0xE1A00000 // NOP for now
809            }
810
811            // #594: CallIndirect is expanded to a real multi-instruction
812            // sequence by the early return at the top of this function —
813            // it must NEVER fall through to a silent NOP again.
814            ArmOp::CallIndirect { .. } => {
815                unreachable!("CallIndirect handled by encode_arm_call_indirect (#594)")
816            }
817
818            // i64 pseudo-instructions (Phase 2) - encode as NOP for now
819            // Real compiler would expand these to multi-instruction sequences
820            ArmOp::I64Add { .. } => 0xE1A00000,        // NOP
821            ArmOp::I64Sub { .. } => 0xE1A00000,        // NOP
822            ArmOp::I64DivS { .. } => 0xE1A00000,       // NOP
823            ArmOp::I64DivU { .. } => 0xE1A00000,       // NOP
824            ArmOp::I64RemS { .. } => 0xE1A00000,       // NOP
825            ArmOp::I64RemU { .. } => 0xE1A00000,       // NOP
826            ArmOp::I64Clz { .. } => 0xE1A00000,        // NOP
827            ArmOp::I64Ctz { .. } => 0xE1A00000,        // NOP
828            ArmOp::I64Popcnt { .. } => 0xE1A00000,     // NOP
829            ArmOp::I64And { .. } => 0xE1A00000,        // NOP
830            ArmOp::I64Or { .. } => 0xE1A00000,         // NOP
831            ArmOp::I64Xor { .. } => 0xE1A00000,        // NOP
832            ArmOp::I64Eqz { .. } => 0xE1A00000,        // NOP
833            ArmOp::I64Eq { .. } => 0xE1A00000,         // NOP
834            ArmOp::I64Ne { .. } => 0xE1A00000,         // NOP
835            ArmOp::I64LtS { .. } => 0xE1A00000,        // NOP
836            ArmOp::I64LtU { .. } => 0xE1A00000,        // NOP
837            ArmOp::I64LeS { .. } => 0xE1A00000,        // NOP
838            ArmOp::I64LeU { .. } => 0xE1A00000,        // NOP
839            ArmOp::I64GtS { .. } => 0xE1A00000,        // NOP
840            ArmOp::I64GtU { .. } => 0xE1A00000,        // NOP
841            ArmOp::I64GeS { .. } => 0xE1A00000,        // NOP
842            ArmOp::I64GeU { .. } => 0xE1A00000,        // NOP
843            ArmOp::I64Const { .. } => 0xE1A00000,      // NOP
844            ArmOp::I64Ldr { .. } => 0xE1A00000,        // NOP
845            ArmOp::I64Str { .. } => 0xE1A00000,        // NOP
846            ArmOp::I64ExtendI32S { .. } => 0xE1A00000, // NOP
847            ArmOp::I64ExtendI32U { .. } => 0xE1A00000, // NOP
848            ArmOp::I64Extend8S { .. } => 0xE1A00000,   // NOP (Thumb-2 only)
849            ArmOp::I64Extend16S { .. } => 0xE1A00000,  // NOP (Thumb-2 only)
850            ArmOp::I64Extend32S { .. } => 0xE1A00000,  // NOP (Thumb-2 only)
851            ArmOp::I32WrapI64 { .. } => 0xE1A00000,    // NOP
852
853            // f32 VFP single-precision instructions
854            ArmOp::F32Add { sd, sn, sm } => encode_vfp_3reg(0xEE300A00, sd, sn, sm)?,
855            ArmOp::F32Sub { sd, sn, sm } => encode_vfp_3reg(0xEE300A40, sd, sn, sm)?,
856            ArmOp::F32Mul { sd, sn, sm } => encode_vfp_3reg(0xEE200A00, sd, sn, sm)?,
857            ArmOp::F32Div { sd, sn, sm } => encode_vfp_3reg(0xEE800A00, sd, sn, sm)?,
858            ArmOp::F32Abs { sd, sm } => encode_vfp_2reg(0xEEB00AC0, sd, sm)?,
859            ArmOp::F32Neg { sd, sm } => encode_vfp_2reg(0xEEB10A40, sd, sm)?,
860            ArmOp::F32Sqrt { sd, sm } => encode_vfp_2reg(0xEEB10AC0, sd, sm)?,
861
862            // f32 pseudo-ops — multi-instruction sequences
863            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
864            ArmOp::F32Ceil { sd, sm } => {
865                return self.encode_arm_f32_rounding(sd, sm, 0b01); // Round toward +Inf
866            }
867            ArmOp::F32Floor { sd, sm } => {
868                return self.encode_arm_f32_rounding(sd, sm, 0b10); // Round toward -Inf
869            }
870            ArmOp::F32Trunc { sd, sm } => {
871                return self.encode_arm_f32_rounding(sd, sm, 0b11); // VCVT toward zero
872            }
873            ArmOp::F32Nearest { sd, sm } => {
874                return self.encode_arm_f32_rounding(sd, sm, 0b00); // VCVT to nearest
875            }
876            ArmOp::F32Min { sd, sn, sm } => {
877                return self.encode_arm_f32_minmax(sd, sn, sm, true);
878            }
879            ArmOp::F32Max { sd, sn, sm } => {
880                return self.encode_arm_f32_minmax(sd, sn, sm, false);
881            }
882            ArmOp::F32Copysign { sd, sn, sm } => {
883                return self.encode_arm_f32_copysign(sd, sn, sm);
884            }
885
886            // f32 comparisons — multi-instruction: VCMP + VMRS + conditional MOV
887            ArmOp::F32Eq { rd, sn, sm } => {
888                return self.encode_arm_f32_compare(rd, sn, sm, 0x0); // EQ
889            }
890            ArmOp::F32Ne { rd, sn, sm } => {
891                return self.encode_arm_f32_compare(rd, sn, sm, 0x1); // NE
892            }
893            ArmOp::F32Lt { rd, sn, sm } => {
894                return self.encode_arm_f32_compare(rd, sn, sm, 0x4); // MI (less than)
895            }
896            ArmOp::F32Le { rd, sn, sm } => {
897                return self.encode_arm_f32_compare(rd, sn, sm, 0x9); // LS (less or same)
898            }
899            ArmOp::F32Gt { rd, sn, sm } => {
900                return self.encode_arm_f32_compare(rd, sn, sm, 0xC); // GT
901            }
902            ArmOp::F32Ge { rd, sn, sm } => {
903                return self.encode_arm_f32_compare(rd, sn, sm, 0xA); // GE
904            }
905
906            // f32 const — multi-instruction: MOVW + MOVT + VMOV
907            ArmOp::F32Const { sd, value } => {
908                return self.encode_arm_f32_const(sd, *value);
909            }
910
911            ArmOp::F32Load { sd, addr } => encode_vfp_ldst(0xED900A00, sd, addr)?,
912            ArmOp::F32Store { sd, addr } => encode_vfp_ldst(0xED800A00, sd, addr)?,
913
914            // f32 conversions — multi-instruction sequences
915            ArmOp::F32ConvertI32S { sd, rm } => {
916                return self.encode_arm_f32_convert_i32(sd, rm, true);
917            }
918            ArmOp::F32ConvertI32U { sd, rm } => {
919                return self.encode_arm_f32_convert_i32(sd, rm, false);
920            }
921            ArmOp::F32ConvertI64S { .. } | ArmOp::F32ConvertI64U { .. } => {
922                return Err(synth_core::Error::synthesis(
923                    "F32 i64 conversion not supported (requires register pairs on 32-bit ARM)",
924                ));
925            }
926            ArmOp::F32ReinterpretI32 { sd, rm } => encode_vmov_core_sreg(true, sd, rm)?,
927            ArmOp::I32ReinterpretF32 { rd, sm } => encode_vmov_core_sreg(false, sm, rd)?,
928            ArmOp::I32TruncF32S { rd, sm } => {
929                return self.encode_arm_i32_trunc_f32(rd, sm, true);
930            }
931            ArmOp::I32TruncF32U { rd, sm } => {
932                return self.encode_arm_i32_trunc_f32(rd, sm, false);
933            }
934
935            // f64 VFP double-precision instructions (ARM32)
936            // F64 arithmetic: same as F32 but with sz=1 (bit 8 = 1, cp11 = 0xB)
937            ArmOp::F64Add { dd, dn, dm } => encode_vfp_3reg_f64(0xEE300B00, dd, dn, dm)?,
938            ArmOp::F64Sub { dd, dn, dm } => encode_vfp_3reg_f64(0xEE300B40, dd, dn, dm)?,
939            ArmOp::F64Mul { dd, dn, dm } => encode_vfp_3reg_f64(0xEE200B00, dd, dn, dm)?,
940            ArmOp::F64Div { dd, dn, dm } => encode_vfp_3reg_f64(0xEE800B00, dd, dn, dm)?,
941            ArmOp::F64Abs { dd, dm } => encode_vfp_2reg_f64(0xEEB00BC0, dd, dm)?,
942            ArmOp::F64Neg { dd, dm } => encode_vfp_2reg_f64(0xEEB10B40, dd, dm)?,
943            ArmOp::F64Sqrt { dd, dm } => encode_vfp_2reg_f64(0xEEB10BC0, dd, dm)?,
944
945            // f64 pseudo-ops
946            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
947            ArmOp::F64Ceil { dd, dm } => {
948                return self.encode_arm_f64_rounding(dd, dm, 0b01);
949            }
950            ArmOp::F64Floor { dd, dm } => {
951                return self.encode_arm_f64_rounding(dd, dm, 0b10);
952            }
953            ArmOp::F64Trunc { dd, dm } => {
954                return self.encode_arm_f64_rounding(dd, dm, 0b11);
955            }
956            ArmOp::F64Nearest { dd, dm } => {
957                return self.encode_arm_f64_rounding(dd, dm, 0b00);
958            }
959            ArmOp::F64Min { dd, dn, dm } => {
960                return self.encode_arm_f64_minmax(dd, dn, dm, true);
961            }
962            ArmOp::F64Max { dd, dn, dm } => {
963                return self.encode_arm_f64_minmax(dd, dn, dm, false);
964            }
965            ArmOp::F64Copysign { dd, dn, dm } => {
966                return self.encode_arm_f64_copysign(dd, dn, dm);
967            }
968
969            // f64 comparisons
970            ArmOp::F64Eq { rd, dn, dm } => {
971                return self.encode_arm_f64_compare(rd, dn, dm, 0x0);
972            }
973            ArmOp::F64Ne { rd, dn, dm } => {
974                return self.encode_arm_f64_compare(rd, dn, dm, 0x1);
975            }
976            ArmOp::F64Lt { rd, dn, dm } => {
977                return self.encode_arm_f64_compare(rd, dn, dm, 0x4);
978            }
979            ArmOp::F64Le { rd, dn, dm } => {
980                return self.encode_arm_f64_compare(rd, dn, dm, 0x9);
981            }
982            ArmOp::F64Gt { rd, dn, dm } => {
983                return self.encode_arm_f64_compare(rd, dn, dm, 0xC);
984            }
985            ArmOp::F64Ge { rd, dn, dm } => {
986                return self.encode_arm_f64_compare(rd, dn, dm, 0xA);
987            }
988
989            ArmOp::F64Const { dd, value } => {
990                return self.encode_arm_f64_const(dd, *value);
991            }
992
993            ArmOp::F64Load { dd, addr } => encode_vfp_ldst_f64(0xED900B00, dd, addr)?,
994            ArmOp::F64Store { dd, addr } => encode_vfp_ldst_f64(0xED800B00, dd, addr)?,
995
996            ArmOp::F64ConvertI32S { dd, rm } => {
997                return self.encode_arm_f64_convert_i32(dd, rm, true);
998            }
999            ArmOp::F64ConvertI32U { dd, rm } => {
1000                return self.encode_arm_f64_convert_i32(dd, rm, false);
1001            }
1002            ArmOp::F64ConvertI64S { .. } | ArmOp::F64ConvertI64U { .. } => {
1003                return Err(synth_core::Error::synthesis(
1004                    "F64 i64 conversion not supported (requires register pairs on 32-bit ARM)",
1005                ));
1006            }
1007            ArmOp::F64PromoteF32 { dd, sm } => {
1008                return self.encode_arm_f64_promote_f32(dd, sm);
1009            }
1010            ArmOp::F64ReinterpretI64 { dd, rmlo, rmhi } => {
1011                encode_vmov_core_dreg(true, dd, rmlo, rmhi)?
1012            }
1013            ArmOp::I64ReinterpretF64 { rdlo, rdhi, dm } => {
1014                encode_vmov_core_dreg(false, dm, rdlo, rdhi)?
1015            }
1016            ArmOp::I64TruncF64S { .. } | ArmOp::I64TruncF64U { .. } => {
1017                return Err(synth_core::Error::synthesis(
1018                    "i64 truncation from F64 not supported (requires i64 register pairs on 32-bit ARM)",
1019                ));
1020            }
1021            ArmOp::I32TruncF64S { rd, dm } => {
1022                return self.encode_arm_i32_trunc_f64(rd, dm, true);
1023            }
1024            ArmOp::I32TruncF64U { rd, dm } => {
1025                return self.encode_arm_i32_trunc_f64(rd, dm, false);
1026            }
1027            // Multi-instruction sequences - only meaningful in Thumb-2 mode
1028            ArmOp::I64SetCond { .. }
1029            | ArmOp::I64SetCondZ { .. }
1030            | ArmOp::I64Mul { .. }
1031            | ArmOp::I64Shl { .. }
1032            | ArmOp::I64ShrS { .. }
1033            | ArmOp::I64ShrU { .. }
1034            | ArmOp::I64Rotl { .. }
1035            | ArmOp::I64Rotr { .. } => 0xE1A00000, // NOP (Thumb-2 only)
1036
1037            // MVE instructions — Thumb-2 only (Cortex-M55 is always Thumb-2)
1038            ArmOp::MveLoad { .. }
1039            | ArmOp::MveStore { .. }
1040            | ArmOp::MveConst { .. }
1041            | ArmOp::MveAnd { .. }
1042            | ArmOp::MveOrr { .. }
1043            | ArmOp::MveEor { .. }
1044            | ArmOp::MveMvn { .. }
1045            | ArmOp::MveBic { .. }
1046            | ArmOp::MveAddI { .. }
1047            | ArmOp::MveSubI { .. }
1048            | ArmOp::MveMulI { .. }
1049            | ArmOp::MveNegI { .. }
1050            | ArmOp::MveCmpEqI { .. }
1051            | ArmOp::MveCmpNeI { .. }
1052            | ArmOp::MveCmpLtS { .. }
1053            | ArmOp::MveCmpLtU { .. }
1054            | ArmOp::MveCmpGtS { .. }
1055            | ArmOp::MveCmpGtU { .. }
1056            | ArmOp::MveCmpLeS { .. }
1057            | ArmOp::MveCmpLeU { .. }
1058            | ArmOp::MveCmpGeS { .. }
1059            | ArmOp::MveCmpGeU { .. }
1060            | ArmOp::MveDup { .. }
1061            | ArmOp::MveExtractLane { .. }
1062            | ArmOp::MveInsertLane { .. }
1063            | ArmOp::MveAddF32 { .. }
1064            | ArmOp::MveSubF32 { .. }
1065            | ArmOp::MveMulF32 { .. }
1066            | ArmOp::MveNegF32 { .. }
1067            | ArmOp::MveAbsF32 { .. }
1068            | ArmOp::MveCmpEqF32 { .. }
1069            | ArmOp::MveCmpNeF32 { .. }
1070            | ArmOp::MveCmpLtF32 { .. }
1071            | ArmOp::MveCmpLeF32 { .. }
1072            | ArmOp::MveCmpGtF32 { .. }
1073            | ArmOp::MveCmpGeF32 { .. }
1074            | ArmOp::MveDupF32 { .. }
1075            | ArmOp::MveExtractLaneF32 { .. }
1076            | ArmOp::MveReplaceLaneF32 { .. }
1077            | ArmOp::MveDivF32 { .. }
1078            | ArmOp::MveSqrtF32 { .. } => 0xE1A00000, // NOP (MVE = Thumb-2 only)
1079        };
1080
1081        // ARM32 instructions are little-endian
1082        Ok(instr.to_le_bytes().to_vec())
1083    }
1084
1085    // === ARM32 VFP multi-instruction helpers ===
1086
1087    /// Encode F32 comparison as ARM32: VCMP.F32 + VMRS + MOV rd,#0 + MOVcond rd,#1
1088    fn encode_arm_f32_compare(
1089        &self,
1090        rd: &Reg,
1091        sn: &VfpReg,
1092        sm: &VfpReg,
1093        cond_code: u32,
1094    ) -> Result<Vec<u8>> {
1095        let mut bytes = Vec::new();
1096
1097        // VCMP.F32 Sn, Sm: 0xEEB40A40 with Sn in Vd position, Sm in Vm position
1098        let sn_num = vfp_sreg_to_num(sn)?;
1099        let sm_num = vfp_sreg_to_num(sm)?;
1100        let (vd, d) = encode_sreg(sn_num);
1101        let (vm, m) = encode_sreg(sm_num);
1102        let vcmp = 0xEEB40A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1103        bytes.extend_from_slice(&vcmp.to_le_bytes());
1104
1105        // VMRS APSR_nzcv, FPSCR: 0xEEF1FA10
1106        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1107
1108        // MOV rd, #0: 0xE3A0_0000 | (rd << 12)
1109        let rd_bits = reg_to_bits(rd);
1110        let mov_zero = 0xE3A00000 | (rd_bits << 12);
1111        bytes.extend_from_slice(&mov_zero.to_le_bytes());
1112
1113        // MOVcond rd, #1: cond(4) | 0011 1010 0000 rd(4) 0000 0000 0001
1114        let mov_one = (cond_code << 28) | 0x03A00001 | (rd_bits << 12);
1115        bytes.extend_from_slice(&mov_one.to_le_bytes());
1116
1117        Ok(bytes)
1118    }
1119
1120    /// Encode F32 constant load as ARM32: MOVW Rt,#lo16 + MOVT Rt,#hi16 + VMOV Sd,Rt
1121    fn encode_arm_f32_const(&self, sd: &VfpReg, value: f32) -> Result<Vec<u8>> {
1122        let mut bytes = Vec::new();
1123        let bits = value.to_bits();
1124
1125        // Use R12 as temp register for constant loading
1126        let rt: u32 = 12; // R12/IP
1127
1128        // MOVW R12, #lo16: 0xE300_C000 | (imm4 << 16) | imm12
1129        let lo16 = bits & 0xFFFF;
1130        let movw = 0xE3000000 | (rt << 12) | ((lo16 >> 12) << 16) | (lo16 & 0xFFF);
1131        bytes.extend_from_slice(&movw.to_le_bytes());
1132
1133        // MOVT R12, #hi16: 0xE340_C000 | (imm4 << 16) | imm12
1134        let hi16 = (bits >> 16) & 0xFFFF;
1135        let movt = 0xE3400000 | (rt << 12) | ((hi16 >> 12) << 16) | (hi16 & 0xFFF);
1136        bytes.extend_from_slice(&movt.to_le_bytes());
1137
1138        // VMOV Sd, R12
1139        let vmov = encode_vmov_core_sreg(true, sd, &Reg::R12)?;
1140        bytes.extend_from_slice(&vmov.to_le_bytes());
1141
1142        Ok(bytes)
1143    }
1144
1145    /// Encode VMOV + VCVT.F32.S32/U32 as ARM32
1146    fn encode_arm_f32_convert_i32(&self, sd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
1147        let mut bytes = Vec::new();
1148
1149        // VMOV Sd, Rm — move integer to VFP register
1150        let vmov = encode_vmov_core_sreg(true, sd, rm)?;
1151        bytes.extend_from_slice(&vmov.to_le_bytes());
1152
1153        // VCVT.F32.S32 Sd, Sd (signed) or VCVT.F32.U32 Sd, Sd (unsigned)
1154        // Base: 0xEEB80A40 (signed) or 0xEEB80AC0 (unsigned)
1155        let sd_num = vfp_sreg_to_num(sd)?;
1156        let (vd, d) = encode_sreg(sd_num);
1157        let (vm, m) = encode_sreg(sd_num); // same register as source
1158        let base = if signed { 0xEEB80A40 } else { 0xEEB80AC0 };
1159        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
1160        bytes.extend_from_slice(&vcvt.to_le_bytes());
1161
1162        Ok(bytes)
1163    }
1164
1165    /// Encode F32 rounding pseudo-op as ARM32 via VCVT to integer and back.
1166    /// mode: 0b00=nearest, 0b01=floor(-Inf), 0b10=ceil(+Inf), 0b11=trunc(zero)
1167    /// Strategy: VCVT.S32.F32 Sd, Sm (toward zero), then VCVT.F32.S32 Sd, Sd
1168    /// For ceil/floor/nearest, we use VCVTR (round toward mode) + convert back.
1169    /// Simplified: convert to int (toward zero for trunc) then back to float.
1170    /// Encode F32 rounding as ARM32.
1171    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
1172    ///
1173    /// For trunc (mode=0b11): uses VCVTR.S32.F32 (always rounds toward zero).
1174    /// For ceil/floor/nearest: sets FPSCR rounding mode, uses VCVT.S32.F32 (non-R variant
1175    /// which honours FPSCR rmode), then restores FPSCR.
1176    fn encode_arm_f32_rounding(&self, sd: &VfpReg, sm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
1177        let mut bytes = Vec::new();
1178        let sm_num = vfp_sreg_to_num(sm)?;
1179        let sd_num = vfp_sreg_to_num(sd)?;
1180        let (vd_s, d_s) = encode_sreg(sd_num);
1181        let (vm_s, m_s) = encode_sreg(sm_num);
1182
1183        if mode == 0b11 {
1184            // Trunc (toward zero): VCVTR.S32.F32 — the "R" variant always truncates.
1185            // 0xEEBD0AC0: bit[7]=1 => round toward zero regardless of FPSCR
1186            let vcvt_to_int = 0xEEBD0AC0 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
1187            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1188        } else {
1189            // ceil/floor/nearest: manipulate FPSCR rounding mode
1190            let rt: u32 = 12; // R12/IP as temp
1191
1192            // VMRS R12, FPSCR
1193            let vmrs = 0xEEF10A10 | (rt << 12);
1194            bytes.extend_from_slice(&vmrs.to_le_bytes());
1195
1196            // BIC R12, R12, #(3 << 22) — clear RMode bits [23:22]
1197            // 3<<22 = 0x00C00000. ARM rotated imm: 0x03 ror 10 (rotation=5, imm8=0x03)
1198            let bic = 0xE3CC0000 | (rt << 12) | (0x05 << 8) | 0x03;
1199            bytes.extend_from_slice(&bic.to_le_bytes());
1200
1201            // ORR R12, R12, #(mode << 22) — set desired rounding mode
1202            if mode != 0 {
1203                // mode<<22: rotation=5, imm8=mode
1204                let orr = 0xE38C0000 | (rt << 12) | (0x05 << 8) | (mode as u32);
1205                bytes.extend_from_slice(&orr.to_le_bytes());
1206            }
1207
1208            // VMSR FPSCR, R12
1209            let vmsr = 0xEEE10A10 | (rt << 12);
1210            bytes.extend_from_slice(&vmsr.to_le_bytes());
1211
1212            // VCVT.S32.F32 Sd, Sm — non-R variant (bit[7]=0), uses FPSCR rounding mode
1213            let vcvt_to_int = 0xEEBD0A40 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
1214            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1215
1216            // Restore FPSCR: clear rmode bits back to nearest (default)
1217            bytes.extend_from_slice(&vmrs.to_le_bytes());
1218            bytes.extend_from_slice(&bic.to_le_bytes());
1219            bytes.extend_from_slice(&vmsr.to_le_bytes());
1220        }
1221
1222        // VCVT.F32.S32 Sd, Sd (convert integer result back to float)
1223        let (vd2, d2) = encode_sreg(sd_num);
1224        let vcvt_to_float = 0xEEB80A40 | (d2 << 22) | (vd2 << 12) | (d_s << 5) | vd_s;
1225        bytes.extend_from_slice(&vcvt_to_float.to_le_bytes());
1226
1227        Ok(bytes)
1228    }
1229
1230    /// Encode F32 min/max as ARM32: VCMP + VMRS + conditional VMOV
1231    fn encode_arm_f32_minmax(
1232        &self,
1233        sd: &VfpReg,
1234        sn: &VfpReg,
1235        sm: &VfpReg,
1236        is_min: bool,
1237    ) -> Result<Vec<u8>> {
1238        let mut bytes = Vec::new();
1239        let sn_num = vfp_sreg_to_num(sn)?;
1240        let sm_num = vfp_sreg_to_num(sm)?;
1241        let sd_num = vfp_sreg_to_num(sd)?;
1242
1243        // VMOV Sd, Sn (start with first operand)
1244        let (vd, d) = encode_sreg(sd_num);
1245        let (vn, n) = encode_sreg(sn_num);
1246        let vmov_sn = 0xEEB00A40 | (d << 22) | (vd << 12) | (n << 5) | vn;
1247        bytes.extend_from_slice(&vmov_sn.to_le_bytes());
1248
1249        // VCMP.F32 Sn, Sm
1250        let (vm, m) = encode_sreg(sm_num);
1251        let vcmp = 0xEEB40A40 | (n << 22) | (vn << 12) | (m << 5) | vm;
1252        bytes.extend_from_slice(&vcmp.to_le_bytes());
1253
1254        // VMRS APSR_nzcv, FPSCR
1255        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1256
1257        // For min: if Sn > Sm (GT), use Sm. Condition = GT (0xC)
1258        // For max: if Sn < Sm (MI/LT), use Sm. Condition = MI (0x4)
1259        let cond = if is_min { 0xCu32 } else { 0x4u32 };
1260
1261        // VMOV{cond} Sd, Sm — conditional VMOV
1262        let vmov_cond = (cond << 28) | 0x0EB00A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1263        bytes.extend_from_slice(&vmov_cond.to_le_bytes());
1264
1265        Ok(bytes)
1266    }
1267
1268    /// Encode F32 copysign as ARM32: extract sign from Sm, magnitude from Sn
1269    fn encode_arm_f32_copysign(&self, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
1270        let mut bytes = Vec::new();
1271
1272        // VMOV R12, Sm (get sign source bits)
1273        let vmov_sm = encode_vmov_core_sreg(false, sm, &Reg::R12)?;
1274        bytes.extend_from_slice(&vmov_sm.to_le_bytes());
1275
1276        // VMOV R0, Sn (get magnitude source bits) — use R0 as temp
1277        let vmov_sn = encode_vmov_core_sreg(false, sn, &Reg::R0)?;
1278        bytes.extend_from_slice(&vmov_sn.to_le_bytes());
1279
1280        // AND R12, R12, #0x80000000 (keep only sign bit)
1281        // Thumb-2 constant 0x80000000 needs special encoding; in ARM32 use rotated imm
1282        // 0x80000000 = 0x02 rotated right by 2 (rotation=1, imm8=0x02)
1283        let and_sign = 0xE2000000u32 | (12 << 16) | (12 << 12) | (1 << 8) | 0x02;
1284        bytes.extend_from_slice(&and_sign.to_le_bytes());
1285
1286        // BIC R0, R0, #0x80000000 (clear sign bit from magnitude)
1287        // R0 = register 0, so Rn and Rd fields are 0
1288        let bic_sign = 0xE3C00000u32 | (1 << 8) | 0x02;
1289        bytes.extend_from_slice(&bic_sign.to_le_bytes());
1290
1291        // ORR R0, R0, R12 (combine sign + magnitude)
1292        // R0 = register 0, so Rn and Rd fields are 0
1293        let orr = 0xE1800000u32 | 12;
1294        bytes.extend_from_slice(&orr.to_le_bytes());
1295
1296        // VMOV Sd, R0
1297        let vmov_result = encode_vmov_core_sreg(true, sd, &Reg::R0)?;
1298        bytes.extend_from_slice(&vmov_result.to_le_bytes());
1299
1300        Ok(bytes)
1301    }
1302
1303    /// Encode F64 comparison as ARM32: VCMP.F64 + VMRS + MOV rd,#0 + MOVcond rd,#1
1304    fn encode_arm_f64_compare(
1305        &self,
1306        rd: &Reg,
1307        dn: &VfpReg,
1308        dm: &VfpReg,
1309        cond_code: u32,
1310    ) -> Result<Vec<u8>> {
1311        let mut bytes = Vec::new();
1312
1313        // VCMP.F64 Dn, Dm: 0xEEB40B40 with Dn in Vd position, Dm in Vm position
1314        let dn_num = vfp_dreg_to_num(dn)?;
1315        let dm_num = vfp_dreg_to_num(dm)?;
1316        let (vd, d) = encode_dreg(dn_num);
1317        let (vm, m) = encode_dreg(dm_num);
1318        let vcmp = 0xEEB40B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1319        bytes.extend_from_slice(&vcmp.to_le_bytes());
1320
1321        // VMRS APSR_nzcv, FPSCR
1322        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1323
1324        // MOV rd, #0
1325        let rd_bits = reg_to_bits(rd);
1326        let mov_zero = 0xE3A00000 | (rd_bits << 12);
1327        bytes.extend_from_slice(&mov_zero.to_le_bytes());
1328
1329        // MOVcond rd, #1
1330        let mov_one = (cond_code << 28) | 0x03A00001 | (rd_bits << 12);
1331        bytes.extend_from_slice(&mov_one.to_le_bytes());
1332
1333        Ok(bytes)
1334    }
1335
1336    /// Encode F64 constant load as ARM32: MOVW + MOVT + MOVW + MOVT + VMOV
1337    fn encode_arm_f64_const(&self, dd: &VfpReg, value: f64) -> Result<Vec<u8>> {
1338        let mut bytes = Vec::new();
1339        let bits = value.to_bits();
1340        let lo32 = bits as u32;
1341        let hi32 = (bits >> 32) as u32;
1342
1343        // Load low 32 bits into R0 (Rd field = 0 for R0)
1344        let lo16 = lo32 & 0xFFFF;
1345        let movw_r0 = 0xE3000000 | ((lo16 >> 12) << 16) | (lo16 & 0xFFF);
1346        bytes.extend_from_slice(&movw_r0.to_le_bytes());
1347        let hi16 = (lo32 >> 16) & 0xFFFF;
1348        let movt_r0 = 0xE3400000 | ((hi16 >> 12) << 16) | (hi16 & 0xFFF);
1349        bytes.extend_from_slice(&movt_r0.to_le_bytes());
1350
1351        // Load high 32 bits into R12
1352        let lo16 = hi32 & 0xFFFF;
1353        let movw_r12 = 0xE3000000 | ((lo16 >> 12) << 16) | (12 << 12) | (lo16 & 0xFFF);
1354        bytes.extend_from_slice(&movw_r12.to_le_bytes());
1355        let hi16 = (hi32 >> 16) & 0xFFFF;
1356        let movt_r12 = 0xE3400000 | ((hi16 >> 12) << 16) | (12 << 12) | (hi16 & 0xFFF);
1357        bytes.extend_from_slice(&movt_r12.to_le_bytes());
1358
1359        // VMOV Dd, R0, R12
1360        let vmov = encode_vmov_core_dreg(true, dd, &Reg::R0, &Reg::R12)?;
1361        bytes.extend_from_slice(&vmov.to_le_bytes());
1362
1363        Ok(bytes)
1364    }
1365
1366    /// Encode VMOV Sd, Rm + VCVT.F64.S32/U32 Dd, Sd as ARM32
1367    fn encode_arm_f64_convert_i32(&self, dd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
1368        let mut bytes = Vec::new();
1369
1370        // Use S0 as intermediate: VMOV S0, Rm
1371        let vmov = encode_vmov_core_sreg(true, &VfpReg::S0, rm)?;
1372        bytes.extend_from_slice(&vmov.to_le_bytes());
1373
1374        // VCVT.F64.S32 Dd, S0 (signed) or VCVT.F64.U32 Dd, S0 (unsigned)
1375        // Base: 0xEEB80B40 (signed) or 0xEEB80BC0 (unsigned)
1376        let dd_num = vfp_dreg_to_num(dd)?;
1377        let (vd, d) = encode_dreg(dd_num);
1378        let base = if signed { 0xEEB80B40 } else { 0xEEB80BC0 };
1379        // S0 is register 0: Vm=0, M=0
1380        let vcvt = base | (d << 22) | (vd << 12);
1381        bytes.extend_from_slice(&vcvt.to_le_bytes());
1382
1383        Ok(bytes)
1384    }
1385
1386    /// Encode VCVT.F64.F32 Dd, Sm as ARM32 (f32 to f64 promotion)
1387    fn encode_arm_f64_promote_f32(&self, dd: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
1388        let dd_num = vfp_dreg_to_num(dd)?;
1389        let sm_num = vfp_sreg_to_num(sm)?;
1390        let (vd, d) = encode_dreg(dd_num);
1391        let (vm, m) = encode_sreg(sm_num);
1392
1393        // VCVT.F64.F32 Dd, Sm: 0xEEB70AC0
1394        let vcvt = 0xEEB70AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
1395        Ok(vcvt.to_le_bytes().to_vec())
1396    }
1397
1398    /// Encode VCVT.S32/U32.F64 Sd, Dm + VMOV Rd, Sd as ARM32
1399    fn encode_arm_i32_trunc_f64(&self, rd: &Reg, dm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
1400        let mut bytes = Vec::new();
1401        let dm_num = vfp_dreg_to_num(dm)?;
1402        let (vm, m) = encode_dreg(dm_num);
1403
1404        // VCVT.S32.F64 S0, Dm (toward zero) or VCVT.U32.F64 S0, Dm
1405        // S0: Vd=0, D=0
1406        let base = if signed { 0xEEBD0BC0 } else { 0xEEBC0BC0 };
1407        let vcvt = base | (m << 5) | vm;
1408        bytes.extend_from_slice(&vcvt.to_le_bytes());
1409
1410        // VMOV Rd, S0
1411        let vmov = encode_vmov_core_sreg(false, &VfpReg::S0, rd)?;
1412        bytes.extend_from_slice(&vmov.to_le_bytes());
1413
1414        Ok(bytes)
1415    }
1416
1417    /// Encode F64 rounding pseudo-op as ARM32 via VCVT to integer and back.
1418    /// Encode F64 rounding as ARM32.
1419    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
1420    ///
1421    /// For trunc: uses VCVTR.S32.F64 (always truncates).
1422    /// For ceil/floor/nearest: sets FPSCR rounding mode, uses VCVT.S32.F64 (non-R variant),
1423    /// then restores FPSCR.
1424    fn encode_arm_f64_rounding(&self, dd: &VfpReg, dm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
1425        let mut bytes = Vec::new();
1426        let dm_num = vfp_dreg_to_num(dm)?;
1427        let dd_num = vfp_dreg_to_num(dd)?;
1428        let (vm, m) = encode_dreg(dm_num);
1429        let (vd, d) = encode_dreg(dd_num);
1430
1431        if mode == 0b11 {
1432            // Trunc (toward zero): VCVTR.S32.F64 — bit[7]=1, always truncates
1433            let vcvt_to_int = 0xEEBD0BC0 | (m << 5) | vm;
1434            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1435        } else {
1436            // ceil/floor/nearest: manipulate FPSCR rounding mode
1437            let rt: u32 = 12;
1438
1439            // VMRS R12, FPSCR
1440            let vmrs = 0xEEF10A10 | (rt << 12);
1441            bytes.extend_from_slice(&vmrs.to_le_bytes());
1442
1443            // BIC R12, R12, #(3 << 22)
1444            let bic = 0xE3CC0000 | (rt << 12) | (0x05 << 8) | 0x03;
1445            bytes.extend_from_slice(&bic.to_le_bytes());
1446
1447            // ORR R12, R12, #(mode << 22)
1448            if mode != 0 {
1449                let orr = 0xE38C0000 | (rt << 12) | (0x05 << 8) | (mode as u32);
1450                bytes.extend_from_slice(&orr.to_le_bytes());
1451            }
1452
1453            // VMSR FPSCR, R12
1454            let vmsr = 0xEEE10A10 | (rt << 12);
1455            bytes.extend_from_slice(&vmsr.to_le_bytes());
1456
1457            // VCVT.S32.F64 S0, Dm — non-R variant (bit[7]=0), uses FPSCR rmode
1458            let vcvt_to_int = 0xEEBD0B40 | (m << 5) | vm;
1459            bytes.extend_from_slice(&vcvt_to_int.to_le_bytes());
1460
1461            // Restore FPSCR
1462            bytes.extend_from_slice(&vmrs.to_le_bytes());
1463            bytes.extend_from_slice(&bic.to_le_bytes());
1464            bytes.extend_from_slice(&vmsr.to_le_bytes());
1465        }
1466
1467        // VCVT.F64.S32 Dd, S0 (convert back to double)
1468        let vcvt_to_float = 0xEEB80B40 | (d << 22) | (vd << 12);
1469        bytes.extend_from_slice(&vcvt_to_float.to_le_bytes());
1470
1471        Ok(bytes)
1472    }
1473
1474    /// Encode F64 min/max as ARM32: VMOV + VCMP + VMRS + conditional VMOV
1475    fn encode_arm_f64_minmax(
1476        &self,
1477        dd: &VfpReg,
1478        dn: &VfpReg,
1479        dm: &VfpReg,
1480        is_min: bool,
1481    ) -> Result<Vec<u8>> {
1482        let mut bytes = Vec::new();
1483        let dn_num = vfp_dreg_to_num(dn)?;
1484        let dm_num = vfp_dreg_to_num(dm)?;
1485        let dd_num = vfp_dreg_to_num(dd)?;
1486
1487        // VMOV.F64 Dd, Dn (start with first operand)
1488        let (vd, d) = encode_dreg(dd_num);
1489        let (vn, n) = encode_dreg(dn_num);
1490        let vmov_dn = 0xEEB00B40 | (d << 22) | (vd << 12) | (n << 5) | vn;
1491        bytes.extend_from_slice(&vmov_dn.to_le_bytes());
1492
1493        // VCMP.F64 Dn, Dm
1494        let (vm, m) = encode_dreg(dm_num);
1495        let vcmp = 0xEEB40B40 | (n << 22) | (vn << 12) | (m << 5) | vm;
1496        bytes.extend_from_slice(&vcmp.to_le_bytes());
1497
1498        // VMRS APSR_nzcv, FPSCR
1499        bytes.extend_from_slice(&0xEEF1FA10u32.to_le_bytes());
1500
1501        let cond = if is_min { 0xCu32 } else { 0x4u32 };
1502        let vmov_cond = (cond << 28) | 0x0EB00B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
1503        bytes.extend_from_slice(&vmov_cond.to_le_bytes());
1504
1505        Ok(bytes)
1506    }
1507
1508    /// Encode F64 copysign as ARM32
1509    fn encode_arm_f64_copysign(&self, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<Vec<u8>> {
1510        let mut bytes = Vec::new();
1511
1512        // VMOV R0, R12, Dm (get sign source bits)
1513        let vmov_dm = encode_vmov_core_dreg(false, dm, &Reg::R0, &Reg::R12)?;
1514        bytes.extend_from_slice(&vmov_dm.to_le_bytes());
1515
1516        // VMOV R1, R2, Dn (get magnitude source bits)
1517        // We use R1 (lo) and R2 (hi) for the magnitude
1518        let vmov_dn = encode_vmov_core_dreg(false, dn, &Reg::R1, &Reg::R2)?;
1519        bytes.extend_from_slice(&vmov_dn.to_le_bytes());
1520
1521        // AND R12, R12, #0x80000000 (keep only sign bit from hi word)
1522        let and_sign = 0xE2000000u32 | (12 << 16) | (12 << 12) | (1 << 8) | 0x02;
1523        bytes.extend_from_slice(&and_sign.to_le_bytes());
1524
1525        // BIC R2, R2, #0x80000000 (clear sign bit from magnitude hi word)
1526        let bic_sign = 0xE3C00000u32 | (2 << 16) | (2 << 12) | (1 << 8) | 0x02;
1527        bytes.extend_from_slice(&bic_sign.to_le_bytes());
1528
1529        // ORR R2, R2, R12 (combine sign + magnitude)
1530        let orr = 0xE1800000u32 | (2 << 16) | (2 << 12) | 12;
1531        bytes.extend_from_slice(&orr.to_le_bytes());
1532
1533        // VMOV Dd, R1, R2
1534        let vmov_result = encode_vmov_core_dreg(true, dd, &Reg::R1, &Reg::R2)?;
1535        bytes.extend_from_slice(&vmov_result.to_le_bytes());
1536
1537        Ok(bytes)
1538    }
1539
1540    /// Encode VCVT.S32/U32.F32 + VMOV as ARM32
1541    fn encode_arm_i32_trunc_f32(&self, rd: &Reg, sm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
1542        let mut bytes = Vec::new();
1543
1544        // VCVT.S32.F32 Sd, Sm (toward zero) or VCVT.U32.F32 Sd, Sm
1545        // We use Sm as both source and destination for the intermediate result
1546        let sm_num = vfp_sreg_to_num(sm)?;
1547        let (vd, d) = encode_sreg(sm_num);
1548        let (vm, m) = encode_sreg(sm_num);
1549        let base = if signed { 0xEEBD0AC0 } else { 0xEEBC0AC0 };
1550        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
1551        bytes.extend_from_slice(&vcvt.to_le_bytes());
1552
1553        // VMOV Rd, Sm — move result back to core register
1554        let vmov = encode_vmov_core_sreg(false, sm, rd)?;
1555        bytes.extend_from_slice(&vmov.to_le_bytes());
1556
1557        Ok(bytes)
1558    }
1559
1560    /// Encode an ARM instruction in Thumb-2 mode (16-bit or 32-bit instructions)
1561    fn encode_thumb(&self, op: &ArmOp) -> Result<Vec<u8>> {
1562        // Thumb-2 supports both 16-bit and 32-bit instructions
1563        // 32-bit instructions are encoded as two 16-bit halfwords (big-endian order)
1564        match op {
1565            // === 16-bit Thumb encodings ===
1566            ArmOp::Add { rd, rn, op2 } => {
1567                let rd_bits = reg_to_bits(rd) as u16;
1568                let rn_bits = reg_to_bits(rn) as u16;
1569
1570                if let Operand2::Reg(rm) = op2 {
1571                    let rm_bits = reg_to_bits(rm) as u16;
1572                    // 16-bit ADDS only has 3-bit register fields (R0-R7). For
1573                    // high registers (e.g. R12, the MemLoad/MemStore base
1574                    // scratch) the bits overflow into adjacent fields, silently
1575                    // corrupting the operands — issue #178/#180: `add ip,ip,r0`
1576                    // was emitted as `adds r4,r5,r1`. Guard on all three regs
1577                    // being low and fall back to 32-bit ADD.W otherwise, exactly
1578                    // as the Sub handler below does.
1579                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1580                        // ADDS Rd, Rn, Rm (16-bit): 0001 100 Rm Rn Rd
1581                        let instr: u16 = 0x1800 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1582                        Ok(instr.to_le_bytes().to_vec())
1583                    } else {
1584                        // ADD.W Rd, Rn, Rm (32-bit) for high registers
1585                        self.encode_thumb32_add_reg_raw(
1586                            rd_bits as u32,
1587                            rn_bits as u32,
1588                            rm_bits as u32,
1589                        )
1590                    }
1591                } else if let Operand2::Imm(imm) = op2 {
1592                    if *imm <= 7 && rd_bits < 8 && rn_bits < 8 {
1593                        // ADDS Rd, Rn, #imm3 (16-bit): 0001 110 imm3 Rn Rd
1594                        let instr: u16 = 0x1C00 | ((*imm as u16) << 6) | (rn_bits << 3) | rd_bits;
1595                        Ok(instr.to_le_bytes().to_vec())
1596                    } else {
1597                        // Use 32-bit ADD for larger immediates
1598                        self.encode_thumb32_add(rd, rn, *imm as u32)
1599                    }
1600                } else {
1601                    // Fallback to 32-bit encoding
1602                    self.encode_thumb32_add(rd, rn, 0)
1603                }
1604            }
1605
1606            ArmOp::Sub { rd, rn, op2 } => {
1607                let rd_bits = reg_to_bits(rd) as u16;
1608                let rn_bits = reg_to_bits(rn) as u16;
1609
1610                if let Operand2::Reg(rm) = op2 {
1611                    let rm_bits = reg_to_bits(rm) as u16;
1612                    // 16-bit SUBS can only use low registers (R0-R7)
1613                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1614                        // SUBS Rd, Rn, Rm (16-bit): 0001 101 Rm Rn Rd
1615                        let instr: u16 = 0x1A00 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1616                        Ok(instr.to_le_bytes().to_vec())
1617                    } else {
1618                        // Use 32-bit SUB.W for high registers
1619                        self.encode_thumb32_sub_reg_raw(
1620                            rd_bits as u32,
1621                            rn_bits as u32,
1622                            rm_bits as u32,
1623                        )
1624                    }
1625                } else if let Operand2::Imm(imm) = op2 {
1626                    if *imm <= 7 && rd_bits < 8 && rn_bits < 8 {
1627                        // SUBS Rd, Rn, #imm3 (16-bit): 0001 111 imm3 Rn Rd
1628                        let instr: u16 = 0x1E00 | ((*imm as u16) << 6) | (rn_bits << 3) | rd_bits;
1629                        Ok(instr.to_le_bytes().to_vec())
1630                    } else {
1631                        self.encode_thumb32_sub(rd, rn, *imm as u32)
1632                    }
1633                } else {
1634                    self.encode_thumb32_sub(rd, rn, 0)
1635                }
1636            }
1637
1638            ArmOp::Mov { rd, op2 } => {
1639                let rd_bits = reg_to_bits(rd) as u16;
1640
1641                if let Operand2::Imm(imm) = op2 {
1642                    if *imm <= 255 && rd_bits < 8 {
1643                        // MOVS Rd, #imm8 (16-bit): 0010 0 Rd imm8
1644                        let imm_bits = (*imm as u16) & 0xFF;
1645                        let instr: u16 = 0x2000 | (rd_bits << 8) | imm_bits;
1646                        Ok(instr.to_le_bytes().to_vec())
1647                    } else {
1648                        // Use 32-bit MOVW for larger immediates
1649                        self.encode_thumb32_movw(rd, *imm as u32)
1650                    }
1651                } else if let Operand2::Reg(rm) = op2 {
1652                    let rm_bits = reg_to_bits(rm) as u16;
1653                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
1654                    // D = Rd[3], Rd[2:0] in lower bits
1655                    let d_bit = (rd_bits >> 3) & 1;
1656                    let instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
1657                    Ok(instr.to_le_bytes().to_vec())
1658                } else {
1659                    let instr: u16 = 0xBF00; // NOP fallback
1660                    Ok(instr.to_le_bytes().to_vec())
1661                }
1662            }
1663
1664            ArmOp::Push { regs } => {
1665                // Thumb-2 PUSH encoding:
1666                // If all regs in R0-R7 + LR, use 16-bit: 1011 010 M rrrrrrrr
1667                // Otherwise use 32-bit: STMDB SP!, {regs} = 1110 1001 0010 1101 | 0M0 reglist(13)
1668                let mut reg_list: u16 = 0;
1669                let mut need_32bit = false;
1670                for r in regs {
1671                    let bit = reg_to_bits(r);
1672                    if bit >= 8 && *r != Reg::LR {
1673                        need_32bit = true;
1674                    }
1675                    reg_list |= 1 << bit;
1676                }
1677                if !need_32bit {
1678                    // 16-bit PUSH: 1011 010 M rrrrrrrr
1679                    let m_bit = if reg_list & (1 << 14) != 0 {
1680                        1u16
1681                    } else {
1682                        0u16
1683                    };
1684                    let low_regs = reg_list & 0xFF;
1685                    let instr: u16 = 0xB400 | (m_bit << 8) | low_regs;
1686                    Ok(instr.to_le_bytes().to_vec())
1687                } else {
1688                    // 32-bit STMDB SP!, {regs}: E92D | reglist(16)
1689                    let hw1: u16 = 0xE92D;
1690                    let hw2: u16 = reg_list;
1691                    let mut bytes = hw1.to_le_bytes().to_vec();
1692                    bytes.extend_from_slice(&hw2.to_le_bytes());
1693                    Ok(bytes)
1694                }
1695            }
1696
1697            ArmOp::Pop { regs } => {
1698                // Thumb-2 POP encoding:
1699                // If all regs in R0-R7 + PC, use 16-bit: 1011 110 P rrrrrrrr
1700                // Otherwise use 32-bit: LDMIA SP!, {regs} = 1110 1000 1011 1101 | PM0 reglist(13)
1701                let mut reg_list: u16 = 0;
1702                let mut need_32bit = false;
1703                for r in regs {
1704                    let bit = reg_to_bits(r);
1705                    if bit >= 8 && *r != Reg::PC {
1706                        need_32bit = true;
1707                    }
1708                    reg_list |= 1 << bit;
1709                }
1710                if !need_32bit {
1711                    // 16-bit POP: 1011 110 P rrrrrrrr
1712                    let p_bit = if reg_list & (1 << 15) != 0 {
1713                        1u16
1714                    } else {
1715                        0u16
1716                    };
1717                    let low_regs = reg_list & 0xFF;
1718                    let instr: u16 = 0xBC00 | (p_bit << 8) | low_regs;
1719                    Ok(instr.to_le_bytes().to_vec())
1720                } else {
1721                    // 32-bit LDMIA SP!, {regs}: E8BD | reglist(16)
1722                    let hw1: u16 = 0xE8BD;
1723                    let hw2: u16 = reg_list;
1724                    let mut bytes = hw1.to_le_bytes().to_vec();
1725                    bytes.extend_from_slice(&hw2.to_le_bytes());
1726                    Ok(bytes)
1727                }
1728            }
1729
1730            ArmOp::Nop => {
1731                let instr: u16 = 0xBF00; // NOP in Thumb-2
1732                Ok(instr.to_le_bytes().to_vec())
1733            }
1734
1735            ArmOp::Udf { imm } => {
1736                // UDF (Undefined) in Thumb-2: 16-bit encoding is 0xDE00 | imm8
1737                // This triggers UsageFault/HardFault, used for WASM traps
1738                let instr: u16 = 0xDE00 | (*imm as u16);
1739                let bytes = instr.to_le_bytes().to_vec();
1740                encoding_contracts::verify_thumb16(&bytes);
1741                Ok(bytes)
1742            }
1743
1744            // i64 support: ADDS, ADC, SUBS, SBC for register pair arithmetic
1745            // ADDS sets flags (carry), ADC uses carry from previous ADDS
1746            ArmOp::Adds { rd, rn, op2 } => {
1747                let rd_bits = reg_to_bits(rd) as u16;
1748                let rn_bits = reg_to_bits(rn) as u16;
1749
1750                if let Operand2::Reg(rm) = op2 {
1751                    let rm_bits = reg_to_bits(rm) as u16;
1752                    // 16-bit ADDS is R0-R7 only; i64 pair allocation can place
1753                    // operands in R8-R11, which would overflow the 3-bit fields
1754                    // and corrupt the operands (#178/#180 class). Guard and fall
1755                    // back to 32-bit ADDS.W for high registers.
1756                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1757                        // ADDS Rd, Rn, Rm (16-bit): 0001 100 Rm Rn Rd
1758                        let instr: u16 = 0x1800 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1759                        Ok(instr.to_le_bytes().to_vec())
1760                    } else {
1761                        self.encode_thumb32_adds_reg_raw(
1762                            rd_bits as u32,
1763                            rn_bits as u32,
1764                            rm_bits as u32,
1765                        )
1766                    }
1767                } else {
1768                    // 32-bit Thumb-2 ADDS with immediate
1769                    self.encode_thumb32_adds(rd, rn, 0)
1770                }
1771            }
1772
1773            // ADC: Add with Carry (Thumb-2 32-bit)
1774            // ADC.W Rd, Rn, Rm: EB40 Rn | 00 Rd 00 Rm
1775            ArmOp::Adc { rd, rn, op2 } => {
1776                let rd_bits = reg_to_bits(rd);
1777                let rn_bits = reg_to_bits(rn);
1778
1779                if let Operand2::Reg(rm) = op2 {
1780                    let rm_bits = reg_to_bits(rm);
1781                    // ADC.W Rd, Rn, Rm (T2): 1110 1011 0100 Rn | 0 000 Rd 00 00 Rm
1782                    let hw1: u16 = (0xEB40 | rn_bits) as u16;
1783                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1784
1785                    let mut bytes = hw1.to_le_bytes().to_vec();
1786                    bytes.extend_from_slice(&hw2.to_le_bytes());
1787                    Ok(bytes)
1788                } else {
1789                    // ADC with immediate - use 32-bit encoding
1790                    let hw1: u16 = (0xF140 | rn_bits) as u16;
1791                    let hw2: u16 = (rd_bits << 8) as u16;
1792                    let mut bytes = hw1.to_le_bytes().to_vec();
1793                    bytes.extend_from_slice(&hw2.to_le_bytes());
1794                    Ok(bytes)
1795                }
1796            }
1797
1798            // SUBS sets flags (borrow), SBC uses borrow from previous SUBS
1799            ArmOp::Subs { rd, rn, op2 } => {
1800                let rd_bits = reg_to_bits(rd) as u16;
1801                let rn_bits = reg_to_bits(rn) as u16;
1802
1803                if let Operand2::Reg(rm) = op2 {
1804                    let rm_bits = reg_to_bits(rm) as u16;
1805                    // 16-bit SUBS is R0-R7 only; high-register i64 pair operands
1806                    // would overflow the 3-bit fields (#178/#180 class). Guard
1807                    // and fall back to 32-bit SUBS.W for high registers.
1808                    if rd_bits < 8 && rn_bits < 8 && rm_bits < 8 {
1809                        // SUBS Rd, Rn, Rm (16-bit): 0001 101 Rm Rn Rd
1810                        let instr: u16 = 0x1A00 | (rm_bits << 6) | (rn_bits << 3) | rd_bits;
1811                        Ok(instr.to_le_bytes().to_vec())
1812                    } else {
1813                        self.encode_thumb32_subs_reg_raw(
1814                            rd_bits as u32,
1815                            rn_bits as u32,
1816                            rm_bits as u32,
1817                        )
1818                    }
1819                } else {
1820                    // 32-bit Thumb-2 SUBS with immediate
1821                    self.encode_thumb32_subs(rd, rn, 0)
1822                }
1823            }
1824
1825            // SBC: Subtract with Carry (Thumb-2 32-bit)
1826            // SBC.W Rd, Rn, Rm: EB60 Rn | 00 Rd 00 Rm
1827            ArmOp::Sbc { rd, rn, op2 } => {
1828                let rd_bits = reg_to_bits(rd);
1829                let rn_bits = reg_to_bits(rn);
1830
1831                if let Operand2::Reg(rm) = op2 {
1832                    let rm_bits = reg_to_bits(rm);
1833                    // SBC.W Rd, Rn, Rm (T2): 1110 1011 0110 Rn | 0 000 Rd 00 00 Rm
1834                    let hw1: u16 = (0xEB60 | rn_bits) as u16;
1835                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1836
1837                    let mut bytes = hw1.to_le_bytes().to_vec();
1838                    bytes.extend_from_slice(&hw2.to_le_bytes());
1839                    Ok(bytes)
1840                } else {
1841                    // SBC with immediate - use 32-bit encoding
1842                    let hw1: u16 = (0xF160 | rn_bits) as u16;
1843                    let hw2: u16 = (rd_bits << 8) as u16;
1844                    let mut bytes = hw1.to_le_bytes().to_vec();
1845                    bytes.extend_from_slice(&hw2.to_le_bytes());
1846                    Ok(bytes)
1847                }
1848            }
1849
1850            // === 32-bit Thumb-2 encodings ===
1851
1852            // SDIV: 11111011 1001 Rn 1111 Rd 1111 Rm
1853            ArmOp::Sdiv { rd, rn, rm } => {
1854                let rd_bits = reg_to_bits(rd);
1855                let rn_bits = reg_to_bits(rn);
1856                let rm_bits = reg_to_bits(rm);
1857                reg_bits_checked(rd_bits)?;
1858                reg_bits_checked(rn_bits)?;
1859                reg_bits_checked(rm_bits)?;
1860
1861                // Thumb-2 SDIV: FB90 F0F0 | Rn<<16 | Rd<<8 | Rm
1862                // First halfword: 1111 1011 1001 Rn = 0xFB90 | Rn
1863                // Second halfword: 1111 Rd 1111 Rm = 0xF0F0 | Rd<<8 | Rm
1864                let hw1: u16 = (0xFB90 | rn_bits) as u16;
1865                let hw2: u16 = (0xF0F0 | (rd_bits << 8) | rm_bits) as u16;
1866
1867                // Thumb-2 32-bit instructions: first halfword, then second halfword (little-endian each)
1868                let mut bytes = hw1.to_le_bytes().to_vec();
1869                bytes.extend_from_slice(&hw2.to_le_bytes());
1870                encoding_contracts::verify_thumb32(&bytes);
1871                Ok(bytes)
1872            }
1873
1874            // UDIV: 11111011 1011 Rn 1111 Rd 1111 Rm
1875            ArmOp::Udiv { rd, rn, rm } => {
1876                let rd_bits = reg_to_bits(rd);
1877                let rn_bits = reg_to_bits(rn);
1878                let rm_bits = reg_to_bits(rm);
1879                reg_bits_checked(rd_bits)?;
1880                reg_bits_checked(rn_bits)?;
1881                reg_bits_checked(rm_bits)?;
1882
1883                // Thumb-2 UDIV: FBB0 F0F0 | Rn<<16 | Rd<<8 | Rm
1884                let hw1: u16 = (0xFBB0 | rn_bits) as u16;
1885                let hw2: u16 = (0xF0F0 | (rd_bits << 8) | rm_bits) as u16;
1886
1887                let mut bytes = hw1.to_le_bytes().to_vec();
1888                bytes.extend_from_slice(&hw2.to_le_bytes());
1889                encoding_contracts::verify_thumb32(&bytes);
1890                Ok(bytes)
1891            }
1892
1893            ArmOp::Umull { rdlo, rdhi, rn, rm } => {
1894                let rdlo_bits = reg_to_bits(rdlo);
1895                let rdhi_bits = reg_to_bits(rdhi);
1896                let rn_bits = reg_to_bits(rn);
1897                let rm_bits = reg_to_bits(rm);
1898                reg_bits_checked(rdlo_bits)?;
1899                reg_bits_checked(rdhi_bits)?;
1900                reg_bits_checked(rn_bits)?;
1901                reg_bits_checked(rm_bits)?;
1902
1903                // Thumb-2 UMULL: 1111 1011 1010 Rn | RdLo RdHi 0000 Rm
1904                let hw1: u16 = (0xFBA0 | rn_bits) as u16;
1905                let hw2: u16 = ((rdlo_bits << 12) | (rdhi_bits << 8) | rm_bits) as u16;
1906
1907                let mut bytes = hw1.to_le_bytes().to_vec();
1908                bytes.extend_from_slice(&hw2.to_le_bytes());
1909                encoding_contracts::verify_thumb32(&bytes);
1910                Ok(bytes)
1911            }
1912
1913            // MUL (Thumb-2 32-bit): MUL Rd, Rn, Rm
1914            ArmOp::Mul { rd, rn, rm } => {
1915                let rd_bits = reg_to_bits(rd);
1916                let rn_bits = reg_to_bits(rn);
1917                let rm_bits = reg_to_bits(rm);
1918
1919                // Thumb-2 MUL: FB00 F000 | Rn | Rd<<8 | Rm
1920                // 11111011 0000 Rn | 1111 Rd 0000 Rm
1921                let hw1: u16 = (0xFB00 | rn_bits) as u16;
1922                let hw2: u16 = (0xF000 | (rd_bits << 8) | rm_bits) as u16;
1923
1924                let mut bytes = hw1.to_le_bytes().to_vec();
1925                bytes.extend_from_slice(&hw2.to_le_bytes());
1926                Ok(bytes)
1927            }
1928
1929            // MLS: Rd = Ra - Rn * Rm
1930            ArmOp::Mls { rd, rn, rm, ra } => {
1931                let rd_bits = reg_to_bits(rd);
1932                let rn_bits = reg_to_bits(rn);
1933                let rm_bits = reg_to_bits(rm);
1934                let ra_bits = reg_to_bits(ra);
1935
1936                // Thumb-2 MLS: FB00 Rn | Ra Rd 0001 Rm
1937                // 11111011 0000 Rn | Ra Rd 0001 Rm
1938                let hw1: u16 = (0xFB00 | rn_bits) as u16;
1939                let hw2: u16 = ((ra_bits << 12) | (rd_bits << 8) | 0x10 | rm_bits) as u16;
1940
1941                let mut bytes = hw1.to_le_bytes().to_vec();
1942                bytes.extend_from_slice(&hw2.to_le_bytes());
1943                Ok(bytes)
1944            }
1945
1946            ArmOp::Mla { rd, rn, rm, ra } => {
1947                let rd_bits = reg_to_bits(rd);
1948                let rn_bits = reg_to_bits(rn);
1949                let rm_bits = reg_to_bits(rm);
1950                let ra_bits = reg_to_bits(ra);
1951
1952                // Thumb-2 MLA: FB00 Rn | Ra Rd 0000 Rm — same as MLS without the
1953                // bit-4 (0x10) op flag. rd = ra + rn*rm.
1954                let hw1: u16 = (0xFB00 | rn_bits) as u16;
1955                let hw2: u16 = ((ra_bits << 12) | (rd_bits << 8) | rm_bits) as u16;
1956
1957                let mut bytes = hw1.to_le_bytes().to_vec();
1958                bytes.extend_from_slice(&hw2.to_le_bytes());
1959                Ok(bytes)
1960            }
1961
1962            // AND (Thumb-2 32-bit)
1963            ArmOp::And { rd, rn, op2 } => {
1964                if let Operand2::Reg(rm) = op2 {
1965                    let rd_bits = reg_to_bits(rd);
1966                    let rn_bits = reg_to_bits(rn);
1967                    let rm_bits = reg_to_bits(rm);
1968
1969                    // Thumb-2 AND register: EA00 Rn | 0 Rd 00 00 Rm
1970                    let hw1: u16 = (0xEA00 | rn_bits) as u16;
1971                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
1972
1973                    let mut bytes = hw1.to_le_bytes().to_vec();
1974                    bytes.extend_from_slice(&hw2.to_le_bytes());
1975                    Ok(bytes)
1976                } else if let Operand2::Imm(imm) = op2 {
1977                    let rd_bits = reg_to_bits(rd);
1978                    let rn_bits = reg_to_bits(rn);
1979
1980                    // Thumb-2 AND.W immediate T1: 11110 i 0 0000 S Rn | 0 imm3 Rd imm8.
1981                    // The i:imm3:imm8 field is a ThumbExpandImm modified immediate —
1982                    // encode it correctly (or error on an un-encodable value)
1983                    // rather than packing raw bits, closing the silent-miscompile
1984                    // class for AND alongside ORR/EOR (#251) / ADD/SUB (#253) /
1985                    // CMP (#255).
1986                    let field = try_thumb_expand_imm(*imm as u32).ok_or_else(|| {
1987                        synth_core::Error::synthesis(
1988                            "AND immediate is not a valid ThumbExpandImm — materialize into a register",
1989                        )
1990                    })?;
1991                    let i_bit = (field >> 11) & 1;
1992                    let imm3 = (field >> 8) & 0x7;
1993                    let imm8 = field & 0xFF;
1994
1995                    let hw1: u16 = (0xF000 | (i_bit << 10) | rn_bits) as u16;
1996                    let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
1997
1998                    let mut bytes = hw1.to_le_bytes().to_vec();
1999                    bytes.extend_from_slice(&hw2.to_le_bytes());
2000                    Ok(bytes)
2001                } else {
2002                    // RegShift variant - fallback to NOP
2003                    let instr: u16 = 0xBF00;
2004                    Ok(instr.to_le_bytes().to_vec())
2005                }
2006            }
2007
2008            // ORR (Thumb-2 32-bit)
2009            ArmOp::Orr { rd, rn, op2 } => {
2010                if let Operand2::Reg(rm) = op2 {
2011                    let rd_bits = reg_to_bits(rd);
2012                    let rn_bits = reg_to_bits(rn);
2013                    let rm_bits = reg_to_bits(rm);
2014
2015                    // Thumb-2 ORR: EA40 Rn | 0 Rd 00 00 Rm
2016                    let hw1: u16 = (0xEA40 | rn_bits) as u16;
2017                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
2018
2019                    let mut bytes = hw1.to_le_bytes().to_vec();
2020                    bytes.extend_from_slice(&hw2.to_le_bytes());
2021                    Ok(bytes)
2022                } else if let Operand2::Imm(imm) = op2 {
2023                    // ORR.W immediate T1: 11110 i 0 0010 S Rn | 0 imm3 Rd imm8.
2024                    // Only the zero-extended byte form (imm <= 0xFF) is encoded;
2025                    // larger modified immediates need ThumbExpandImm — return an
2026                    // error rather than silently emit a NOP (Ok-or-Err, #180/#185).
2027                    let imm_val = *imm as u32;
2028                    if imm_val > 0xFF {
2029                        return Err(synth_core::Error::synthesis(
2030                            "ORR immediate > 0xFF requires ThumbExpandImm (not yet implemented)",
2031                        ));
2032                    }
2033                    let rd_bits = reg_to_bits(rd);
2034                    let rn_bits = reg_to_bits(rn);
2035                    let hw1: u16 = (0xF040 | rn_bits) as u16;
2036                    let hw2: u16 = ((rd_bits << 8) | (imm_val & 0xFF)) as u16;
2037                    let mut bytes = hw1.to_le_bytes().to_vec();
2038                    bytes.extend_from_slice(&hw2.to_le_bytes());
2039                    Ok(bytes)
2040                } else {
2041                    let instr: u16 = 0xBF00;
2042                    Ok(instr.to_le_bytes().to_vec())
2043                }
2044            }
2045
2046            // EOR (Thumb-2 32-bit)
2047            ArmOp::Eor { rd, rn, op2 } => {
2048                if let Operand2::Reg(rm) = op2 {
2049                    let rd_bits = reg_to_bits(rd);
2050                    let rn_bits = reg_to_bits(rn);
2051                    let rm_bits = reg_to_bits(rm);
2052
2053                    // Thumb-2 EOR: EA80 Rn | 0 Rd 00 00 Rm
2054                    let hw1: u16 = (0xEA80 | rn_bits) as u16;
2055                    let hw2: u16 = ((rd_bits << 8) | rm_bits) as u16;
2056
2057                    let mut bytes = hw1.to_le_bytes().to_vec();
2058                    bytes.extend_from_slice(&hw2.to_le_bytes());
2059                    Ok(bytes)
2060                } else if let Operand2::Imm(imm) = op2 {
2061                    // EOR.W immediate T1: 11110 i 0 0100 S Rn | 0 imm3 Rd imm8.
2062                    // Byte form only (imm <= 0xFF); larger needs ThumbExpandImm —
2063                    // error, not a silent NOP (Ok-or-Err, #180/#185).
2064                    let imm_val = *imm as u32;
2065                    if imm_val > 0xFF {
2066                        return Err(synth_core::Error::synthesis(
2067                            "EOR immediate > 0xFF requires ThumbExpandImm (not yet implemented)",
2068                        ));
2069                    }
2070                    let rd_bits = reg_to_bits(rd);
2071                    let rn_bits = reg_to_bits(rn);
2072                    let hw1: u16 = (0xF080 | rn_bits) as u16;
2073                    let hw2: u16 = ((rd_bits << 8) | (imm_val & 0xFF)) as u16;
2074                    let mut bytes = hw1.to_le_bytes().to_vec();
2075                    bytes.extend_from_slice(&hw2.to_le_bytes());
2076                    Ok(bytes)
2077                } else {
2078                    let instr: u16 = 0xBF00;
2079                    Ok(instr.to_le_bytes().to_vec())
2080                }
2081            }
2082
2083            // Shift operations (16-bit for low registers)
2084            ArmOp::Lsl { rd, rn, shift } => {
2085                let rd_bits = reg_to_bits(rd) as u16;
2086                let rn_bits = reg_to_bits(rn) as u16;
2087                let shift_bits = (*shift as u16) & 0x1F;
2088
2089                if rd_bits < 8 && rn_bits < 8 {
2090                    // LSLS Rd, Rm, #imm5 (16-bit): 0000 0 imm5 Rm Rd
2091                    let instr: u16 = (shift_bits << 6) | (rn_bits << 3) | rd_bits;
2092                    Ok(instr.to_le_bytes().to_vec())
2093                } else {
2094                    // Use 32-bit encoding for high registers
2095                    self.encode_thumb32_shift(rd, rn, *shift, 0b00) // LSL type
2096                }
2097            }
2098
2099            ArmOp::Lsr { rd, rn, shift } => {
2100                let rd_bits = reg_to_bits(rd) as u16;
2101                let rn_bits = reg_to_bits(rn) as u16;
2102                let shift_bits = (*shift as u16) & 0x1F;
2103
2104                if rd_bits < 8 && rn_bits < 8 && shift_bits > 0 {
2105                    // LSRS Rd, Rm, #imm5 (16-bit): 0000 1 imm5 Rm Rd
2106                    let instr: u16 = 0x0800 | (shift_bits << 6) | (rn_bits << 3) | rd_bits;
2107                    Ok(instr.to_le_bytes().to_vec())
2108                } else {
2109                    self.encode_thumb32_shift(rd, rn, *shift, 0b01) // LSR type
2110                }
2111            }
2112
2113            ArmOp::Asr { rd, rn, shift } => {
2114                let rd_bits = reg_to_bits(rd) as u16;
2115                let rn_bits = reg_to_bits(rn) as u16;
2116                let shift_bits = (*shift as u16) & 0x1F;
2117
2118                if rd_bits < 8 && rn_bits < 8 && shift_bits > 0 {
2119                    // ASRS Rd, Rm, #imm5 (16-bit): 0001 0 imm5 Rm Rd
2120                    let instr: u16 = 0x1000 | (shift_bits << 6) | (rn_bits << 3) | rd_bits;
2121                    Ok(instr.to_le_bytes().to_vec())
2122                } else {
2123                    self.encode_thumb32_shift(rd, rn, *shift, 0b10) // ASR type
2124                }
2125            }
2126
2127            ArmOp::Ror { rd, rn, shift } => {
2128                // ROR doesn't have a 16-bit immediate form, use 32-bit
2129                self.encode_thumb32_shift(rd, rn, *shift, 0b11) // ROR type
2130            }
2131
2132            // Register-based shifts (Thumb-2 32-bit)
2133            // Encoding: 11111010 0xxS Rn 1111 Rd 0000 Rm
2134            // xx = shift type: 00=LSL, 01=LSR, 10=ASR, 11=ROR
2135            ArmOp::LslReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b00),
2136            ArmOp::LsrReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b01),
2137            ArmOp::AsrReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b10),
2138            ArmOp::RorReg { rd, rn, rm } => self.encode_thumb32_shift_reg(rd, rn, rm, 0b11),
2139
2140            // RSB (Reverse Subtract): Rd = imm - Rn
2141            // Thumb-2 T2 encoding: 11110 i 0 1110 S Rn | 0 imm3 Rd imm8
2142            ArmOp::Rsb { rd, rn, imm } => {
2143                let rd_bits = reg_to_bits(rd);
2144                let rn_bits = reg_to_bits(rn);
2145                let imm_val = *imm;
2146
2147                let i_bit = (imm_val >> 11) & 1;
2148                let imm3 = (imm_val >> 8) & 0x7;
2149                let imm8 = imm_val & 0xFF;
2150
2151                // hw1: 11110 i 01110 0 Rn  (S=0)
2152                let hw1: u16 = (0xF1C0 | (i_bit << 10) | rn_bits) as u16;
2153                // hw2: 0 imm3 Rd imm8
2154                let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
2155
2156                let mut bytes = hw1.to_le_bytes().to_vec();
2157                bytes.extend_from_slice(&hw2.to_le_bytes());
2158                Ok(bytes)
2159            }
2160
2161            // CLZ (Thumb-2 32-bit)
2162            ArmOp::Clz { rd, rm } => {
2163                let rd_bits = reg_to_bits(rd);
2164                let rm_bits = reg_to_bits(rm);
2165
2166                // Thumb-2 CLZ: FAB0 Rm | F8 Rd Rm
2167                // 11111010 1011 Rm | 1111 1000 Rd Rm
2168                let hw1: u16 = (0xFAB0 | rm_bits) as u16;
2169                let hw2: u16 = (0xF080 | (rd_bits << 8) | rm_bits) as u16;
2170
2171                let mut bytes = hw1.to_le_bytes().to_vec();
2172                bytes.extend_from_slice(&hw2.to_le_bytes());
2173                Ok(bytes)
2174            }
2175
2176            // RBIT (Thumb-2 32-bit)
2177            ArmOp::Rbit { rd, rm } => {
2178                let rd_bits = reg_to_bits(rd);
2179                let rm_bits = reg_to_bits(rm);
2180
2181                // Thumb-2 RBIT: FA90 Rm | F0 Rd A0 Rm
2182                // 11111010 1001 Rm | 1111 Rd 1010 Rm
2183                let hw1: u16 = (0xFA90 | rm_bits) as u16;
2184                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rm_bits) as u16;
2185
2186                let mut bytes = hw1.to_le_bytes().to_vec();
2187                bytes.extend_from_slice(&hw2.to_le_bytes());
2188                Ok(bytes)
2189            }
2190
2191            // SXTB (16-bit for low registers)
2192            ArmOp::Sxtb { rd, rm } => {
2193                let rd_bits = reg_to_bits(rd) as u16;
2194                let rm_bits = reg_to_bits(rm) as u16;
2195
2196                if rd_bits < 8 && rm_bits < 8 {
2197                    // SXTB Rd, Rm (16-bit): 1011 0010 01 Rm Rd
2198                    let instr: u16 = 0xB240 | (rm_bits << 3) | rd_bits;
2199                    Ok(instr.to_le_bytes().to_vec())
2200                } else {
2201                    // Thumb-2 SXTB.W: FA4F F(rd)80 (rm)
2202                    // 11111010 0100 1111 | 1111 Rd 10 rotate Rm
2203                    let rd_bits32 = rd_bits as u32;
2204                    let rm_bits32 = rm_bits as u32;
2205                    let hw1: u16 = 0xFA4F;
2206                    let hw2: u16 = (0xF080 | (rd_bits32 << 8) | rm_bits32) as u16;
2207                    let mut bytes = hw1.to_le_bytes().to_vec();
2208                    bytes.extend_from_slice(&hw2.to_le_bytes());
2209                    Ok(bytes)
2210                }
2211            }
2212
2213            // SXTH (16-bit for low registers)
2214            ArmOp::Sxth { rd, rm } => {
2215                let rd_bits = reg_to_bits(rd) as u16;
2216                let rm_bits = reg_to_bits(rm) as u16;
2217
2218                if rd_bits < 8 && rm_bits < 8 {
2219                    // SXTH Rd, Rm (16-bit): 1011 0010 00 Rm Rd
2220                    let instr: u16 = 0xB200 | (rm_bits << 3) | rd_bits;
2221                    Ok(instr.to_le_bytes().to_vec())
2222                } else {
2223                    // Thumb-2 SXTH.W: FA0F F(rd)80 (rm)
2224                    // 11111010 0000 1111 | 1111 Rd 10 rotate Rm
2225                    let rd_bits32 = rd_bits as u32;
2226                    let rm_bits32 = rm_bits as u32;
2227                    let hw1: u16 = 0xFA0F;
2228                    let hw2: u16 = (0xF080 | (rd_bits32 << 8) | rm_bits32) as u16;
2229                    let mut bytes = hw1.to_le_bytes().to_vec();
2230                    bytes.extend_from_slice(&hw2.to_le_bytes());
2231                    Ok(bytes)
2232                }
2233            }
2234
2235            // UXTB Rd,Rm — zero-extend byte (rd = rm & 0xff)
2236            ArmOp::Uxtb { rd, rm } => {
2237                let rd_bits = reg_to_bits(rd) as u16;
2238                let rm_bits = reg_to_bits(rm) as u16;
2239                if rd_bits < 8 && rm_bits < 8 {
2240                    // UXTB Rd, Rm (16-bit): 1011 0010 11 Rm Rd
2241                    let instr: u16 = 0xB2C0 | (rm_bits << 3) | rd_bits;
2242                    Ok(instr.to_le_bytes().to_vec())
2243                } else {
2244                    // Thumb-2 UXTB.W: FA5F F(rd)80 (rm)
2245                    let hw1: u16 = 0xFA5F;
2246                    let hw2: u16 = (0xF080 | ((rd_bits as u32) << 8) | rm_bits as u32) as u16;
2247                    let mut bytes = hw1.to_le_bytes().to_vec();
2248                    bytes.extend_from_slice(&hw2.to_le_bytes());
2249                    Ok(bytes)
2250                }
2251            }
2252
2253            // UXTH Rd,Rm — zero-extend halfword (rd = rm & 0xffff)
2254            ArmOp::Uxth { rd, rm } => {
2255                let rd_bits = reg_to_bits(rd) as u16;
2256                let rm_bits = reg_to_bits(rm) as u16;
2257                if rd_bits < 8 && rm_bits < 8 {
2258                    // UXTH Rd, Rm (16-bit): 1011 0010 10 Rm Rd
2259                    let instr: u16 = 0xB280 | (rm_bits << 3) | rd_bits;
2260                    Ok(instr.to_le_bytes().to_vec())
2261                } else {
2262                    // Thumb-2 UXTH.W: FA1F F(rd)80 (rm)
2263                    let hw1: u16 = 0xFA1F;
2264                    let hw2: u16 = (0xF080 | ((rd_bits as u32) << 8) | rm_bits as u32) as u16;
2265                    let mut bytes = hw1.to_le_bytes().to_vec();
2266                    bytes.extend_from_slice(&hw2.to_le_bytes());
2267                    Ok(bytes)
2268                }
2269            }
2270
2271            // CMP (can be 16-bit for low registers)
2272            ArmOp::Cmp { rn, op2 } => {
2273                let rn_bits = reg_to_bits(rn) as u16;
2274
2275                if let Operand2::Imm(imm) = op2 {
2276                    // Only use 16-bit encoding for non-negative immediates 0-255
2277                    // Negative immediates must use 32-bit encoding
2278                    if *imm >= 0 && *imm <= 255 && rn_bits < 8 {
2279                        // CMP Rn, #imm8 (16-bit): 0010 1 Rn imm8
2280                        let instr: u16 = 0x2800 | (rn_bits << 8) | (*imm as u16 & 0xFF);
2281                        Ok(instr.to_le_bytes().to_vec())
2282                    } else {
2283                        self.encode_thumb32_cmp_imm(rn, *imm as u32)
2284                    }
2285                } else if let Operand2::Reg(rm) = op2 {
2286                    let rm_bits = reg_to_bits(rm) as u16;
2287                    if rn_bits < 8 && rm_bits < 8 {
2288                        // CMP Rn, Rm (16-bit low): 0100 0010 10 Rm Rn
2289                        let instr: u16 = 0x4280 | (rm_bits << 3) | rn_bits;
2290                        Ok(instr.to_le_bytes().to_vec())
2291                    } else {
2292                        // CMP Rn, Rm (16-bit high): 0100 0101 N Rm Rn[2:0]
2293                        let n_bit = (rn_bits >> 3) & 1;
2294                        let instr: u16 = 0x4500 | (n_bit << 7) | (rm_bits << 3) | (rn_bits & 0x7);
2295                        Ok(instr.to_le_bytes().to_vec())
2296                    }
2297                } else {
2298                    let instr: u16 = 0xBF00;
2299                    Ok(instr.to_le_bytes().to_vec())
2300                }
2301            }
2302
2303            // CMN (Compare Negative) - computes Rn + op2 and sets flags
2304            // CMN Rn, #1 sets Z flag if Rn == -1 (since -1 + 1 = 0)
2305            ArmOp::Cmn { rn, op2 } => {
2306                let rn_bits = reg_to_bits(rn) as u16;
2307
2308                if let Operand2::Imm(imm) = op2 {
2309                    // CMN.W Rn, #imm (32-bit): i:imm3:imm8 is a ThumbExpandImm
2310                    // modified immediate (the field sits in imm3=hw2[14:12],
2311                    // imm8=hw2[7:0], i=hw1[10]). Encode it correctly, or error on
2312                    // an un-encodable value — replacing the old silent `0xBF00`
2313                    // NOP (the last of the silent-miscompile data-proc encoders).
2314                    let field = try_thumb_expand_imm(*imm as u32).ok_or_else(|| {
2315                        synth_core::Error::synthesis(
2316                            "CMN immediate is not a valid ThumbExpandImm — materialize into a register",
2317                        )
2318                    })?;
2319                    let i_bit = (field >> 11) & 1;
2320                    let imm3 = (field >> 8) & 0x7;
2321                    let imm8 = field & 0xFF;
2322                    let hw1: u16 = (0xF110 | (i_bit << 10) as u16) | rn_bits;
2323                    let hw2: u16 = (imm3 << 12) as u16 | 0x0F00 | imm8 as u16;
2324                    let mut bytes = hw1.to_le_bytes().to_vec();
2325                    bytes.extend_from_slice(&hw2.to_le_bytes());
2326                    Ok(bytes)
2327                } else if let Operand2::Reg(rm) = op2 {
2328                    let rm_bits = reg_to_bits(rm) as u16;
2329                    // 16-bit CMN (T1) only encodes R0-R7; high registers overflow
2330                    // the 3-bit fields and corrupt the operands (#184, the #180
2331                    // class). CMN has no high-register 16-bit form, so fall back
2332                    // to 32-bit CMN.W (T2): EB10 Rn | 0F00 Rm (ADD.W with S=1 and
2333                    // Rd discarded as PC/1111).
2334                    if rn_bits < 8 && rm_bits < 8 {
2335                        // CMN Rn, Rm (16-bit): 0100 0010 11 Rm Rn
2336                        let instr: u16 = 0x42C0 | (rm_bits << 3) | rn_bits;
2337                        Ok(instr.to_le_bytes().to_vec())
2338                    } else {
2339                        let hw1: u16 = 0xEB10 | rn_bits;
2340                        let hw2: u16 = 0x0F00 | rm_bits;
2341                        let mut bytes = hw1.to_le_bytes().to_vec();
2342                        bytes.extend_from_slice(&hw2.to_le_bytes());
2343                        Ok(bytes)
2344                    }
2345                } else {
2346                    Ok(vec![0xBF, 0x00])
2347                }
2348            }
2349
2350            // LDR (can be 16-bit for simple cases)
2351            ArmOp::Ldr { rd, addr } => {
2352                let rd_bits = reg_to_bits(rd);
2353                let base_bits = reg_to_bits(&addr.base);
2354
2355                // Handle register offset mode [base, Roff] or [base, Roff, #imm]
2356                if let Some(offset_reg) = &addr.offset_reg {
2357                    let rm_bits = reg_to_bits(offset_reg);
2358
2359                    // If there's also an immediate offset, we need to ADD it first
2360                    if addr.offset != 0 {
2361                        // Use R12 (IP) as scratch to avoid clobbering the address register
2362                        // ADD R12, Rm, #offset; LDR Rd, [base, R12]
2363                        let scratch = Reg::R12;
2364                        let mut bytes =
2365                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2366                        bytes.extend(self.encode_thumb32_ldr_reg(rd, &addr.base, &scratch)?);
2367                        return Ok(bytes);
2368                    }
2369
2370                    // Simple register offset: LDR Rd, [Rn, Rm]
2371                    // 16-bit: only if Rd, Rn, Rm < R8
2372                    if rd_bits < 8 && base_bits < 8 && rm_bits < 8 {
2373                        // LDR Rd, [Rn, Rm] (16-bit): 0101 100 Rm Rn Rd
2374                        let instr: u16 = 0x5800
2375                            | ((rm_bits as u16) << 6)
2376                            | ((base_bits as u16) << 3)
2377                            | (rd_bits as u16);
2378                        return Ok(instr.to_le_bytes().to_vec());
2379                    }
2380
2381                    // 32-bit register offset
2382                    return self.encode_thumb32_ldr_reg(rd, &addr.base, offset_reg);
2383                }
2384
2385                // Immediate offset mode [base, #imm]
2386                let offset = addr.offset as u32;
2387
2388                if rd_bits < 8 && base_bits < 8 && (offset & 0x3) == 0 && offset <= 124 {
2389                    // LDR Rd, [Rn, #imm5*4] (16-bit): 0110 1 imm5 Rn Rd
2390                    let imm5 = (offset >> 2) as u16;
2391                    let instr: u16 =
2392                        0x6800 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2393                    Ok(instr.to_le_bytes().to_vec())
2394                } else {
2395                    self.encode_thumb32_ldr(rd, &addr.base, offset)
2396                }
2397            }
2398
2399            // STR (can be 16-bit for simple cases)
2400            ArmOp::Str { rd, addr } => {
2401                let rd_bits = reg_to_bits(rd);
2402                let base_bits = reg_to_bits(&addr.base);
2403
2404                // Handle register offset mode [base, Roff] or [base, Roff, #imm]
2405                if let Some(offset_reg) = &addr.offset_reg {
2406                    let rm_bits = reg_to_bits(offset_reg);
2407
2408                    // If there's also an immediate offset, we need to ADD it first
2409                    if addr.offset != 0 {
2410                        // Use R12 (IP) as scratch to avoid clobbering the address register
2411                        // ADD R12, Rm, #offset; STR Rd, [base, R12]
2412                        let scratch = Reg::R12;
2413                        let mut bytes =
2414                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2415                        bytes.extend(self.encode_thumb32_str_reg(rd, &addr.base, &scratch)?);
2416                        return Ok(bytes);
2417                    }
2418
2419                    // Simple register offset: STR Rd, [Rn, Rm]
2420                    // 16-bit: only if Rd, Rn, Rm < R8
2421                    if rd_bits < 8 && base_bits < 8 && rm_bits < 8 {
2422                        // STR Rd, [Rn, Rm] (16-bit): 0101 000 Rm Rn Rd
2423                        let instr: u16 = 0x5000
2424                            | ((rm_bits as u16) << 6)
2425                            | ((base_bits as u16) << 3)
2426                            | (rd_bits as u16);
2427                        return Ok(instr.to_le_bytes().to_vec());
2428                    }
2429
2430                    // 32-bit register offset
2431                    return self.encode_thumb32_str_reg(rd, &addr.base, offset_reg);
2432                }
2433
2434                // Immediate offset mode [base, #imm]
2435                let offset = addr.offset as u32;
2436
2437                if rd_bits < 8 && base_bits < 8 && (offset & 0x3) == 0 && offset <= 124 {
2438                    // STR Rd, [Rn, #imm5*4] (16-bit): 0110 0 imm5 Rn Rd
2439                    let imm5 = (offset >> 2) as u16;
2440                    let instr: u16 =
2441                        0x6000 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2442                    Ok(instr.to_le_bytes().to_vec())
2443                } else {
2444                    self.encode_thumb32_str(rd, &addr.base, offset)
2445                }
2446            }
2447
2448            // LDRB (Thumb-2)
2449            ArmOp::Ldrb { rd, addr } => {
2450                let rd_bits = reg_to_bits(rd);
2451                let base_bits = reg_to_bits(&addr.base);
2452
2453                if let Some(offset_reg) = &addr.offset_reg {
2454                    if addr.offset != 0 {
2455                        let scratch = Reg::R12;
2456                        let mut bytes =
2457                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2458                        bytes.extend(self.encode_thumb32_ldrb_reg(rd, &addr.base, &scratch)?);
2459                        return Ok(bytes);
2460                    }
2461                    return self.encode_thumb32_ldrb_reg(rd, &addr.base, offset_reg);
2462                }
2463
2464                let offset = addr.offset as u32;
2465                if rd_bits < 8 && base_bits < 8 && offset <= 31 {
2466                    // LDRB Rd, [Rn, #imm5] (16-bit): 0111 1 imm5 Rn Rd
2467                    let instr: u16 = 0x7800
2468                        | ((offset as u16) << 6)
2469                        | ((base_bits as u16) << 3)
2470                        | (rd_bits as u16);
2471                    Ok(instr.to_le_bytes().to_vec())
2472                } else {
2473                    self.encode_thumb32_ldrb_imm(rd, &addr.base, offset)
2474                }
2475            }
2476
2477            // LDRSB (Thumb-2)
2478            ArmOp::Ldrsb { rd, addr } => {
2479                let rd_bits = reg_to_bits(rd);
2480                let base_bits = reg_to_bits(&addr.base);
2481
2482                if let Some(offset_reg) = &addr.offset_reg {
2483                    if addr.offset != 0 {
2484                        let scratch = Reg::R12;
2485                        let mut bytes =
2486                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2487                        bytes.extend(self.encode_thumb32_ldrsb_reg(rd, &addr.base, &scratch)?);
2488                        return Ok(bytes);
2489                    }
2490                    return self.encode_thumb32_ldrsb_reg(rd, &addr.base, offset_reg);
2491                }
2492
2493                let offset = addr.offset as u32;
2494                // LDRSB has no 16-bit immediate form (only register)
2495                // For 16-bit reg form: only if Rd, Rn, Rm < R8
2496                if rd_bits < 8 && base_bits < 8 && offset == 0 {
2497                    // No immediate 16-bit encoding for LDRSB; use 32-bit
2498                    self.encode_thumb32_ldrsb_imm(rd, &addr.base, offset)
2499                } else {
2500                    self.encode_thumb32_ldrsb_imm(rd, &addr.base, offset)
2501                }
2502            }
2503
2504            // LDRH (Thumb-2)
2505            ArmOp::Ldrh { rd, addr } => {
2506                let rd_bits = reg_to_bits(rd);
2507                let base_bits = reg_to_bits(&addr.base);
2508
2509                if let Some(offset_reg) = &addr.offset_reg {
2510                    if addr.offset != 0 {
2511                        let scratch = Reg::R12;
2512                        let mut bytes =
2513                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2514                        bytes.extend(self.encode_thumb32_ldrh_reg(rd, &addr.base, &scratch)?);
2515                        return Ok(bytes);
2516                    }
2517                    return self.encode_thumb32_ldrh_reg(rd, &addr.base, offset_reg);
2518                }
2519
2520                let offset = addr.offset as u32;
2521                if rd_bits < 8 && base_bits < 8 && (offset & 0x1) == 0 && offset <= 62 {
2522                    // LDRH Rd, [Rn, #imm5*2] (16-bit): 1000 1 imm5 Rn Rd
2523                    let imm5 = (offset >> 1) as u16;
2524                    let instr: u16 =
2525                        0x8800 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2526                    Ok(instr.to_le_bytes().to_vec())
2527                } else {
2528                    self.encode_thumb32_ldrh_imm(rd, &addr.base, offset)
2529                }
2530            }
2531
2532            // LDRSH (Thumb-2)
2533            ArmOp::Ldrsh { rd, addr } => {
2534                if let Some(offset_reg) = &addr.offset_reg {
2535                    if addr.offset != 0 {
2536                        let scratch = Reg::R12;
2537                        let mut bytes =
2538                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2539                        bytes.extend(self.encode_thumb32_ldrsh_reg(rd, &addr.base, &scratch)?);
2540                        return Ok(bytes);
2541                    }
2542                    return self.encode_thumb32_ldrsh_reg(rd, &addr.base, offset_reg);
2543                }
2544
2545                let offset = addr.offset as u32;
2546                self.encode_thumb32_ldrsh_imm(rd, &addr.base, offset)
2547            }
2548
2549            // STRB (Thumb-2)
2550            ArmOp::Strb { rd, addr } => {
2551                let rd_bits = reg_to_bits(rd);
2552                let base_bits = reg_to_bits(&addr.base);
2553
2554                if let Some(offset_reg) = &addr.offset_reg {
2555                    if addr.offset != 0 {
2556                        let scratch = Reg::R12;
2557                        let mut bytes =
2558                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2559                        bytes.extend(self.encode_thumb32_strb_reg(rd, &addr.base, &scratch)?);
2560                        return Ok(bytes);
2561                    }
2562                    return self.encode_thumb32_strb_reg(rd, &addr.base, offset_reg);
2563                }
2564
2565                let offset = addr.offset as u32;
2566                if rd_bits < 8 && base_bits < 8 && offset <= 31 {
2567                    // STRB Rd, [Rn, #imm5] (16-bit): 0111 0 imm5 Rn Rd
2568                    let instr: u16 = 0x7000
2569                        | ((offset as u16) << 6)
2570                        | ((base_bits as u16) << 3)
2571                        | (rd_bits as u16);
2572                    Ok(instr.to_le_bytes().to_vec())
2573                } else {
2574                    self.encode_thumb32_strb_imm(rd, &addr.base, offset)
2575                }
2576            }
2577
2578            // STRH (Thumb-2)
2579            ArmOp::Strh { rd, addr } => {
2580                let rd_bits = reg_to_bits(rd);
2581                let base_bits = reg_to_bits(&addr.base);
2582
2583                if let Some(offset_reg) = &addr.offset_reg {
2584                    if addr.offset != 0 {
2585                        let scratch = Reg::R12;
2586                        let mut bytes =
2587                            self.encode_thumb32_add_imm(&scratch, offset_reg, addr.offset as u32)?;
2588                        bytes.extend(self.encode_thumb32_strh_reg(rd, &addr.base, &scratch)?);
2589                        return Ok(bytes);
2590                    }
2591                    return self.encode_thumb32_strh_reg(rd, &addr.base, offset_reg);
2592                }
2593
2594                let offset = addr.offset as u32;
2595                if rd_bits < 8 && base_bits < 8 && (offset & 0x1) == 0 && offset <= 62 {
2596                    // STRH Rd, [Rn, #imm5*2] (16-bit): 1000 0 imm5 Rn Rd
2597                    let imm5 = (offset >> 1) as u16;
2598                    let instr: u16 =
2599                        0x8000 | (imm5 << 6) | ((base_bits as u16) << 3) | (rd_bits as u16);
2600                    Ok(instr.to_le_bytes().to_vec())
2601                } else {
2602                    self.encode_thumb32_strh_imm(rd, &addr.base, offset)
2603                }
2604            }
2605
2606            // MemorySize (Thumb-2)
2607            ArmOp::MemorySize { rd } => {
2608                // LSR rd, R10, #16 — memory size in bytes / 65536 = pages
2609                // Thumb-2 16-bit: LSRS Rd, Rm, #imm5 — 0000 1 imm5 Rm Rd
2610                let rd_bits = reg_to_bits(rd);
2611                let r10_bits = reg_to_bits(&Reg::R10);
2612                if rd_bits < 8 && r10_bits < 8 {
2613                    let instr: u16 =
2614                        0x0800 | (16u16 << 6) | ((r10_bits as u16) << 3) | (rd_bits as u16);
2615                    Ok(instr.to_le_bytes().to_vec())
2616                } else {
2617                    // Thumb-2 32-bit LSR: 1110 1010 010 0 1111 | 0 imm3 Rd imm2 01 Rm
2618                    let imm5: u32 = 16;
2619                    let imm3 = (imm5 >> 2) & 0x7;
2620                    let imm2 = imm5 & 0x3;
2621                    let hw1: u16 = 0xEA4F;
2622                    let hw2: u16 =
2623                        ((imm3 << 12) | (rd_bits << 8) | (imm2 << 6) | 0x10 | r10_bits) as u16;
2624                    let mut bytes = hw1.to_le_bytes().to_vec();
2625                    bytes.extend_from_slice(&hw2.to_le_bytes());
2626                    Ok(bytes)
2627                }
2628            }
2629
2630            // MemoryGrow (Thumb-2)
2631            ArmOp::MemoryGrow { rd, .. } => {
2632                // On embedded with fixed memory, always return -1 (failure)
2633                // MVN rd, #0 → MOV rd, #-1
2634                // Thumb-2 32-bit: MVN: 1111 0 i 0 0 0 1 1 0 1111 | 0 imm3 Rd imm8
2635                let rd_bits = reg_to_bits(rd);
2636                let hw1: u16 = 0xF06F; // MVN with i=0
2637                let hw2: u16 = (rd_bits << 8) as u16; // imm8=0 → ~0 = 0xFFFFFFFF = -1
2638                let mut bytes = hw1.to_le_bytes().to_vec();
2639                bytes.extend_from_slice(&hw2.to_le_bytes());
2640                Ok(bytes)
2641            }
2642
2643            // BX (16-bit)
2644            ArmOp::Bx { rm } => {
2645                let rm_bits = reg_to_bits(rm) as u16;
2646                // BX Rm (16-bit): 0100 0111 0 Rm 000
2647                let instr: u16 = 0x4700 | (rm_bits << 3);
2648                Ok(instr.to_le_bytes().to_vec())
2649            }
2650
2651            // BLX (16-bit) - Branch with Link and Exchange
2652            // BLX Rm: 0100 0111 1 Rm 000
2653            ArmOp::Blx { rm } => {
2654                let rm_bits = reg_to_bits(rm) as u16;
2655                let instr: u16 = 0x4780 | (rm_bits << 3);
2656                Ok(instr.to_le_bytes().to_vec())
2657            }
2658
2659            // CallIndirect - indirect function call via table lookup
2660            // table_index_reg contains the table index
2661            // Generates: LSL R12, idx, #2; LDR R12, [R12, table_base]; BLX R12
2662            ArmOp::CallIndirect {
2663                rd: _,
2664                type_idx: _,
2665                table_index_reg,
2666            } => {
2667                let idx_reg = reg_to_bits(table_index_reg);
2668                let mut bytes = Vec::new();
2669
2670                // For now, we generate code that:
2671                // 1. Multiplies index by 4 (function pointer size)
2672                // 2. Loads function pointer from table (assumes table base in R11)
2673                // 3. Calls the function via BLX
2674                //
2675                // Table base setup must be done by caller/runtime.
2676                // This is a simplified implementation - full support needs:
2677                // - Table base address resolution
2678                // - Type signature checking
2679                // - Bounds checking
2680
2681                // LSL R12, idx_reg, #2 (multiply index by 4)
2682                // Thumb-2 MOV with shift: 11101010 010 S 1111 | 0 imm3 Rd imm2 type Rm
2683                // LSL: type=00 (bits 5:4), imm5=2 -> imm3=000, imm2=10 (bits 7:6)
2684                // #597: the shift amount was previously shifted into bits 5:4 —
2685                // the TYPE field — encoding `mov.w ip, rm, ASR #32`, which
2686                // destroyed the index and dispatched table entry 0 for every
2687                // call. imm2 lives at bits 7:6.
2688                let hw1: u16 = 0xEA4F_u16; // MOV.W R12, Rm, LSL #2
2689                let hw2: u16 = ((0x0C00 | (0b10 << 6)) | idx_reg) as u16;
2690                bytes.extend_from_slice(&hw1.to_le_bytes());
2691                bytes.extend_from_slice(&hw2.to_le_bytes());
2692
2693                // LDR R12, [R11, R12] - load function pointer
2694                // Thumb-2 LDR (register): 1111 1000 0101 Rn | Rt 0000 00 imm2 Rm
2695                // Rn=R11, Rt=R12, Rm=R12, imm2=00 (no shift)
2696                let ldr_hw1: u16 = 0xF85B; // LDR.W Rt, [R11, Rm]
2697                let ldr_hw2: u16 = 0xC00C; // Rt=R12, imm2=00, Rm=R12
2698                bytes.extend_from_slice(&ldr_hw1.to_le_bytes());
2699                bytes.extend_from_slice(&ldr_hw2.to_le_bytes());
2700
2701                // BLX R12 (call function indirectly)
2702                // BLX Rm (16-bit): 0100 0111 1 Rm 000
2703                let blx: u16 = 0x47E0; // BLX R12
2704                bytes.extend_from_slice(&blx.to_le_bytes());
2705
2706                Ok(bytes)
2707            }
2708
2709            // Label pseudo-instruction: emits no machine code
2710            ArmOp::Label { .. } => Ok(Vec::new()),
2711
2712            // Conditional branch to label (generic) - offset 0, will be patched
2713            ArmOp::Bcc { cond, label: _ } => {
2714                use synth_synthesis::Condition;
2715                let cond_bits: u16 = match cond {
2716                    Condition::EQ => 0x0,
2717                    Condition::NE => 0x1,
2718                    Condition::HS => 0x2,
2719                    Condition::LO => 0x3,
2720                    Condition::HI => 0x8,
2721                    Condition::LS => 0x9,
2722                    Condition::GE => 0xA,
2723                    Condition::LT => 0xB,
2724                    Condition::GT => 0xC,
2725                    Condition::LE => 0xD,
2726                };
2727                // 16-bit B<cond> with offset 0: 1101 cond imm8
2728                let instr: u16 = 0xD000 | (cond_bits << 8);
2729                Ok(instr.to_le_bytes().to_vec())
2730            }
2731
2732            // Branch instructions
2733            ArmOp::B { label: _ } => {
2734                // Simplified: B.N with offset 0
2735                // For real usage, would need label resolution
2736                let instr: u16 = 0xE000; // B.N #0
2737                Ok(instr.to_le_bytes().to_vec())
2738            }
2739
2740            // BHS (Branch if Higher or Same) - used for bounds checking
2741            // Condition code: 0x2 (C set)
2742            ArmOp::Bhs { label: _ } => {
2743                // 16-bit B<cond> with offset 0: 1101 cond imm8
2744                // cond = 0x2 (HS)
2745                let instr: u16 = 0xD200; // BHS.N #0
2746                Ok(instr.to_le_bytes().to_vec())
2747            }
2748
2749            // BLO (Branch if Lower) - complementary to BHS
2750            // Condition code: 0x3 (C clear)
2751            ArmOp::Blo { label: _ } => {
2752                // 16-bit B<cond> with offset 0: 1101 cond imm8
2753                // cond = 0x3 (LO)
2754                let instr: u16 = 0xD300; // BLO.N #0
2755                Ok(instr.to_le_bytes().to_vec())
2756            }
2757
2758            // Branch with numeric offset (Thumb-2)
2759            // Thumb-2 B.W instruction: 32-bit with +-16MB range
2760            ArmOp::BOffset { offset } => {
2761                // offset is already the halfword displacement: (target - branch - 4) / 2
2762                // This is the raw encoded value, accounting for variable-length instructions
2763                let halfword_offset = *offset;
2764
2765                // 16-bit B.N encoding: 1110 0 imm11 (11-bit signed halfword offset)
2766                // Range: -1024 to +1022 halfwords
2767                if (-1024..=1022).contains(&halfword_offset) {
2768                    // 16-bit B.N encoding: 1110 0 imm11
2769                    let imm11 = (halfword_offset as u16) & 0x7FF;
2770                    let instr: u16 = 0xE000 | imm11;
2771                    Ok(instr.to_le_bytes().to_vec())
2772                } else {
2773                    // 32-bit B.W encoding for larger offsets
2774                    // First halfword: 1111 0 S imm10
2775                    // Second halfword: 10 J1 0 J2 imm11
2776                    // Total offset = SignExtend(S:I1:I2:imm10:imm11:0)
2777                    // where I1 = NOT(J1 XOR S), I2 = NOT(J2 XOR S)
2778
2779                    // The B.W (T4) encoding packs the signed offset as:
2780                    //   S:I1:I2:imm10:imm11:0  (25-bit signed, halfword-aligned)
2781                    // where J1 = NOT(I1 XOR S), J2 = NOT(I2 XOR S)
2782                    // Input halfword_offset already equals (target - PC - 4) / 2,
2783                    // so the full byte offset = halfword_offset << 1.
2784                    // The encoding fields split that 25-bit signed value (including the
2785                    // implicit trailing zero) as: S | imm10 | imm11
2786                    // with I1 = bit 23 and I2 = bit 22 of the signed offset.
2787                    let signed_offset = halfword_offset << 1; // byte offset
2788                    let s = if signed_offset < 0 { 1u32 } else { 0u32 };
2789                    let uoffset = signed_offset as u32;
2790                    let imm10 = (uoffset >> 12) & 0x3FF; // bits [21:12]
2791                    let imm11 = (uoffset >> 1) & 0x7FF; // bits [11:1]
2792                    let i1 = (uoffset >> 23) & 1; // bit 23
2793                    let i2 = (uoffset >> 22) & 1; // bit 22
2794                    let j1 = (!(i1 ^ s)) & 1; // J1 = NOT(I1 XOR S)
2795                    let j2 = (!(i2 ^ s)) & 1; // J2 = NOT(I2 XOR S)
2796
2797                    let hw1: u16 = (0xF000 | (s << 10) | imm10) as u16;
2798                    let hw2: u16 = (0x9000 | (j1 << 13) | (j2 << 11) | imm11) as u16;
2799
2800                    let mut bytes = hw1.to_le_bytes().to_vec();
2801                    bytes.extend_from_slice(&hw2.to_le_bytes());
2802                    Ok(bytes)
2803                }
2804            }
2805
2806            // Conditional branch with numeric offset (Thumb-2)
2807            ArmOp::BCondOffset { cond, offset } => {
2808                use synth_synthesis::Condition;
2809                let cond_bits: u16 = match cond {
2810                    Condition::EQ => 0x0,
2811                    Condition::NE => 0x1,
2812                    Condition::HS => 0x2,
2813                    Condition::LO => 0x3,
2814                    Condition::HI => 0x8,
2815                    Condition::LS => 0x9,
2816                    Condition::GE => 0xA,
2817                    Condition::LT => 0xB,
2818                    Condition::GT => 0xC,
2819                    Condition::LE => 0xD,
2820                };
2821
2822                // offset is already the halfword displacement: (target - branch - 4) / 2
2823                // This is the raw imm8 value for 16-bit B<cond> encoding
2824                let halfword_offset = *offset;
2825
2826                // 16-bit B<cond> encoding: 1101 cond imm8
2827                // Range: -256 to +254 halfwords (imm8 is sign-extended and shifted left 1)
2828                if (-128..=127).contains(&halfword_offset) {
2829                    let imm8 = (halfword_offset as u16) & 0xFF;
2830                    let instr: u16 = 0xD000 | (cond_bits << 8) | imm8;
2831                    Ok(instr.to_le_bytes().to_vec())
2832                } else {
2833                    // 32-bit B<cond>.W for larger offsets
2834                    // First halfword: 1111 0 S cond imm6
2835                    // Second halfword: 10 J1 0 J2 imm11
2836                    let offset = halfword_offset >> 1;
2837                    let s = if offset < 0 { 1u32 } else { 0u32 };
2838                    let imm6 = ((offset >> 11) as u32) & 0x3F;
2839                    let imm11 = (offset as u32) & 0x7FF;
2840                    let j1 = if s == 1 { 1 } else { 0 };
2841                    let j2 = if s == 1 { 1 } else { 0 };
2842
2843                    let hw1: u16 = (0xF000 | (s << 10) | ((cond_bits as u32) << 6) | imm6) as u16;
2844                    let hw2: u16 = (0x8000 | (j1 << 13) | (j2 << 11) | imm11) as u16;
2845
2846                    let mut bytes = hw1.to_le_bytes().to_vec();
2847                    bytes.extend_from_slice(&hw2.to_le_bytes());
2848                    Ok(bytes)
2849                }
2850            }
2851
2852            ArmOp::Bl { label: _ } => {
2853                // BL is always 32-bit in Thumb-2, encoded here as a relocatable
2854                // placeholder; an R_ARM_THM_CALL relocation patches the target
2855                // (see arm_backend.rs). The placeholder must carry an embedded
2856                // addend of -4 so the relocation nets to exactly the symbol S.
2857                //
2858                // Thumb BL computes `target = (P + 4) + signed_offset`. Under
2859                // R_ARM_THM_CALL the linker resolves using the in-place addend;
2860                // a 0xF800 placeholder (addend 0) lands at S+4 — every call one
2861                // instruction past the callee entry (#174). The correct
2862                // placeholder is what `gas` emits for `bl <extern>`:
2863                //   f7ff fffe  ->  `bl <self>`  (S=1, J1=J2=1, imm = -4 addend),
2864                // i.e. hw1=0xF7FF, hw2=0xFFFE. This nets to S, not S+4.
2865                // (The earlier 0xD000 was worse still — a ~+0x600000 addend,
2866                // the garbage `bl c0000c` and "truncated to fit" of #167.)
2867                let hw1: u16 = 0xF7FF;
2868                let hw2: u16 = 0xFFFE;
2869                let mut bytes = hw1.to_le_bytes().to_vec();
2870                bytes.extend_from_slice(&hw2.to_le_bytes());
2871                Ok(bytes)
2872            }
2873
2874            // MVN
2875            ArmOp::Mvn { rd, op2 } => {
2876                if let Operand2::Reg(rm) = op2 {
2877                    let rd_bits = reg_to_bits(rd) as u16;
2878                    let rm_bits = reg_to_bits(rm) as u16;
2879
2880                    if rd_bits < 8 && rm_bits < 8 {
2881                        // MVNS Rd, Rm (16-bit): 0100 0011 11 Rm Rd
2882                        let instr: u16 = 0x43C0 | (rm_bits << 3) | rd_bits;
2883                        Ok(instr.to_le_bytes().to_vec())
2884                    } else {
2885                        // 32-bit MVN
2886                        let hw1: u16 = 0xEA6F_u16;
2887                        let hw2: u16 = ((reg_to_bits(rd) << 8) | reg_to_bits(rm)) as u16;
2888                        let mut bytes = hw1.to_le_bytes().to_vec();
2889                        bytes.extend_from_slice(&hw2.to_le_bytes());
2890                        Ok(bytes)
2891                    }
2892                } else {
2893                    let instr: u16 = 0xBF00;
2894                    Ok(instr.to_le_bytes().to_vec())
2895                }
2896            }
2897
2898            // MOVW - Move Wide (Thumb-2 32-bit)
2899            ArmOp::Movw { rd, imm16 } => {
2900                self.encode_thumb32_movw_raw(reg_to_bits(rd), *imm16 as u32)
2901            }
2902
2903            // MOVT - Move Top (Thumb-2 32-bit)
2904            ArmOp::Movt { rd, imm16 } => {
2905                self.encode_thumb32_movt_raw(reg_to_bits(rd), *imm16 as u32)
2906            }
2907
2908            // #237: symbol-relative MOVW/MOVT. Encode the addend's low/high 16
2909            // bits in place; the backend records an R_ARM_MOVW_ABS_NC /
2910            // R_ARM_MOVT_ABS relocation against `symbol`, so the linker adds the
2911            // symbol's final address to the in-place addend (REL semantics).
2912            ArmOp::MovwSym { rd, addend, .. } => {
2913                self.encode_thumb32_movw_raw(reg_to_bits(rd), (*addend as u32) & 0xffff)
2914            }
2915            ArmOp::MovtSym { rd, addend, .. } => {
2916                self.encode_thumb32_movt_raw(reg_to_bits(rd), ((*addend as u32) >> 16) & 0xffff)
2917            }
2918
2919            // #345: literal-pool address load — emit a PLACEHOLDER `LDR.W rd,
2920            // [pc, #0]` (U=1, imm12=0). The backend (arm_backend.rs) places the
2921            // 4-byte pool word at the end of the function, records the R_ARM_ABS32
2922            // relocation against `symbol+addend`, and patches the imm12 with the
2923            // real PC-relative distance once the pool offset is known.
2924            // Encoding T2: 1111 1000 1101 1111 | Rt(4) imm12(12), with the literal
2925            // base = Align(PC,4) and PC = address of this instruction + 4.
2926            ArmOp::LdrSym { rd, .. } => {
2927                let rt = reg_to_bits(rd) as u16;
2928                let hw1: u16 = 0xF8DF; // LDR.W (literal), U=1
2929                let hw2: u16 = rt << 12; // imm12 = 0 placeholder
2930                let mut bytes = Vec::with_capacity(4);
2931                bytes.extend_from_slice(&hw1.to_le_bytes());
2932                bytes.extend_from_slice(&hw2.to_le_bytes());
2933                Ok(bytes)
2934            }
2935
2936            // SetCond: Materialize condition flag into register (0 or 1)
2937            // Strategy: ITE <cond>; MOV Rd, #1; MOV Rd, #0
2938            // IMPORTANT: Must use ITE (If-Then-Else) because 16-bit Thumb MOV
2939            // always sets flags (MOVS). We need to evaluate the condition BEFORE
2940            // any MOV instruction clobbers the flags from CMP.
2941            ArmOp::SetCond { rd, cond } => {
2942                let rd_bits = reg_to_bits(rd) as u16;
2943
2944                // Condition code encoding for IT block
2945                use synth_synthesis::Condition;
2946                let cond_bits: u16 = match cond {
2947                    Condition::EQ => 0x0,
2948                    Condition::NE => 0x1,
2949                    Condition::LT => 0xB,
2950                    Condition::LE => 0xD,
2951                    Condition::GT => 0xC,
2952                    Condition::GE => 0xA,
2953                    Condition::LO => 0x3, // CC/LO (unsigned <)
2954                    Condition::LS => 0x9, // LS (unsigned <=)
2955                    Condition::HI => 0x8, // HI (unsigned >)
2956                    Condition::HS => 0x2, // CS/HS (unsigned >=)
2957                };
2958
2959                // ITE <cond>: encodes If-Then-Else block
2960                // The mask field depends on firstcond[0]:
2961                // - If firstcond[0] = 0: mask = 0xC for TE pattern (ITE EQ = BF0C)
2962                // - If firstcond[0] = 1: mask = 0x4 for TE pattern (ITE NE = BF14)
2963                let mask = if (cond_bits & 1) == 0 { 0xC } else { 0x4 };
2964                let ite_instr: u16 = 0xBF00 | (cond_bits << 4) | mask;
2965
2966                // Materialize 0/1 into Rd. The 16-bit MOVS (T1) encodes Rd in a
2967                // 3-bit field (bits[10:8]) — only R0–R7. For a high register
2968                // (R8–R12) `rd_bits << 8` overflows into bit 11 and silently
2969                // turns MOVS into CMP (00100 → 00101), corrupting the result
2970                // (this mis-materialized gale's `has_waiter`, so its `local.set`
2971                // stored a stale register → the binary-sem WAKE dispatch read
2972                // garbage). Use the 32-bit MOV.W (T2) for high registers, which
2973                // has a 4-bit Rd field. MOV.W with S=0 doesn't set flags, which
2974                // is fine inside the ITE (the materialized value is the result;
2975                // the flags are not consumed afterwards).
2976                let mut bytes = ite_instr.to_le_bytes().to_vec();
2977                let push_mov = |bytes: &mut Vec<u8>, imm: u16| {
2978                    if rd_bits <= 7 {
2979                        let m: u16 = 0x2000 | (rd_bits << 8) | imm; // 16-bit MOVS Rd,#imm
2980                        bytes.extend_from_slice(&m.to_le_bytes());
2981                    } else {
2982                        // 32-bit MOV.W Rd, #imm (T2): F04F | (Rd<<8) | imm8
2983                        let hw1: u16 = 0xF04F;
2984                        let hw2: u16 = (rd_bits << 8) | imm;
2985                        bytes.extend_from_slice(&hw1.to_le_bytes());
2986                        bytes.extend_from_slice(&hw2.to_le_bytes());
2987                    }
2988                };
2989                push_mov(&mut bytes, 1); // Then branch (condition true)  → 1
2990                push_mov(&mut bytes, 0); // Else branch (condition false) → 0
2991                Ok(bytes)
2992            }
2993
2994            // I64SetCond: Compare two i64 register pairs, result 0/1 in rd
2995            // EQ/NE: CMP lo,lo; IT EQ; CMPEQ hi,hi; ITE <cond>; MOV 1; MOV 0
2996            // LT: CMP lo,lo; SBCS rd,hi,hi; ITE LT; MOV 1; MOV 0
2997            // GT: CMP lo,lo (swapped); SBCS rd,hi,hi (swapped); ITE LT; MOV 1; MOV 0
2998            ArmOp::I64SetCond {
2999                rd,
3000                rn_lo,
3001                rn_hi,
3002                rm_lo,
3003                rm_hi,
3004                cond,
3005            } => {
3006                use synth_synthesis::Condition;
3007                let rd_bits = reg_to_bits(rd) as u16;
3008                let mut bytes = Vec::new();
3009
3010                // Helper: encode CMP Rn, Rm (16-bit)
3011                let encode_cmp_reg = |rn: &synth_synthesis::Reg,
3012                                      rm: &synth_synthesis::Reg|
3013                 -> Vec<u8> {
3014                    let rn_bits = reg_to_bits(rn) as u16;
3015                    let rm_bits = reg_to_bits(rm) as u16;
3016                    if rn_bits < 8 && rm_bits < 8 {
3017                        let instr: u16 = 0x4280 | (rm_bits << 3) | rn_bits;
3018                        instr.to_le_bytes().to_vec()
3019                    } else {
3020                        let n_bit = (rn_bits >> 3) & 1;
3021                        let instr: u16 = 0x4500 | (n_bit << 7) | (rm_bits << 3) | (rn_bits & 0x7);
3022                        instr.to_le_bytes().to_vec()
3023                    }
3024                };
3025
3026                // Helper: encode ITE <cond> (2 bytes)
3027                let encode_ite = |cond_bits: u16| -> Vec<u8> {
3028                    let mask = if (cond_bits & 1) == 0 { 0xC } else { 0x4 };
3029                    let ite_instr: u16 = 0xBF00 | (cond_bits << 4) | mask;
3030                    ite_instr.to_le_bytes().to_vec()
3031                };
3032
3033                // Helper: encode SetCond (ITE + MOV #1 + MOV #0) for given condition
3034                let encode_setcond = |cond_bits: u16, rd_bits: u16| -> Vec<u8> {
3035                    let mut b = encode_ite(cond_bits);
3036                    if rd_bits < 8 {
3037                        let mov_one: u16 = 0x2001 | (rd_bits << 8);
3038                        let mov_zero: u16 = 0x2000 | (rd_bits << 8);
3039                        b.extend_from_slice(&mov_one.to_le_bytes());
3040                        b.extend_from_slice(&mov_zero.to_le_bytes());
3041                    } else {
3042                        // #311: rd >= R8 — the 16-bit MOV imm8 form has a 3-bit
3043                        // rd field; rd_bits<<8 overflows into bit 11 and
3044                        // TRANSMUTES the MOV into CMP (0x2001|0x0800 = 0x2801 =
3045                        // CMP r0,#1): the boolean dies in the flags and the
3046                        // consumer reads a stale register. Use the 32-bit
3047                        // MOV.W (T2: F04F 0000|rd<<8|imm8) — IT-legal,
3048                        // flag-preserving. Same class as H-CODE-9 / #180.
3049                        for imm in [1u16, 0u16] {
3050                            let hw1: u16 = 0xF04F;
3051                            let hw2: u16 = (rd_bits << 8) | imm;
3052                            b.extend_from_slice(&hw1.to_le_bytes());
3053                            b.extend_from_slice(&hw2.to_le_bytes());
3054                        }
3055                    }
3056                    b
3057                };
3058
3059                match cond {
3060                    Condition::EQ | Condition::NE => {
3061                        // CMP rn_lo, rm_lo (compare low words)
3062                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
3063
3064                        // IT EQ (execute next instruction only if Z=1)
3065                        let it_eq: u16 = 0xBF08; // IT EQ: cond=0000, mask=1000
3066                        bytes.extend_from_slice(&it_eq.to_le_bytes());
3067
3068                        // CMPEQ rn_hi, rm_hi (compare high words, only if low equal)
3069                        bytes.extend_from_slice(&encode_cmp_reg(rn_hi, rm_hi));
3070
3071                        // ITE <cond>; MOV rd, #1; MOV rd, #0
3072                        let cond_bits: u16 = match cond {
3073                            Condition::EQ => 0x0,
3074                            Condition::NE => 0x1,
3075                            _ => unreachable!(),
3076                        };
3077                        bytes.extend_from_slice(&encode_setcond(cond_bits, rd_bits));
3078                    }
3079
3080                    Condition::LT => {
3081                        // CMP rn_lo, rm_lo (sets C flag for borrow)
3082                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
3083
3084                        // SBCS rd, rn_hi, rm_hi (subtract with carry, sets N,V flags)
3085                        // SBCS.W Rd, Rn, Rm: EB70 Rn | 0000 Rd 0000 Rm
3086                        let rn_hi_bits = reg_to_bits(rn_hi);
3087                        let rm_hi_bits = reg_to_bits(rm_hi);
3088                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
3089                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
3090                        bytes.extend_from_slice(&hw1.to_le_bytes());
3091                        bytes.extend_from_slice(&hw2.to_le_bytes());
3092
3093                        // ITE LT; MOV rd, #1; MOV rd, #0
3094                        bytes.extend_from_slice(&encode_setcond(0xB, rd_bits)); // LT = 0xB
3095                    }
3096
3097                    Condition::GT => {
3098                        // GT(a,b) = LT(b,a): swap operands
3099                        // CMP rm_lo, rn_lo (swapped)
3100                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
3101
3102                        // SBCS rd, rm_hi, rn_hi (swapped)
3103                        let rm_hi_bits = reg_to_bits(rm_hi);
3104                        let rn_hi_bits = reg_to_bits(rn_hi);
3105                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
3106                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
3107                        bytes.extend_from_slice(&hw1.to_le_bytes());
3108                        bytes.extend_from_slice(&hw2.to_le_bytes());
3109
3110                        // ITE LT; MOV rd, #1; MOV rd, #0
3111                        bytes.extend_from_slice(&encode_setcond(0xB, rd_bits)); // LT = 0xB
3112                    }
3113
3114                    Condition::LE => {
3115                        // LE(a,b) = !GT(a,b): use GT logic but invert result
3116                        // GT(a,b) = LT(b,a): so we do CMP(b,a) and check LT, then invert
3117                        // CMP rm_lo, rn_lo (swapped, same as GT)
3118                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
3119
3120                        // SBCS rd, rm_hi, rn_hi (swapped)
3121                        let rm_hi_bits = reg_to_bits(rm_hi);
3122                        let rn_hi_bits = reg_to_bits(rn_hi);
3123                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
3124                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
3125                        bytes.extend_from_slice(&hw1.to_le_bytes());
3126                        bytes.extend_from_slice(&hw2.to_le_bytes());
3127
3128                        // ITE GE; MOV rd, #1; MOV rd, #0 (GE is !LT, so inverting GT result)
3129                        bytes.extend_from_slice(&encode_setcond(0xA, rd_bits)); // GE = 0xA
3130                    }
3131
3132                    Condition::GE => {
3133                        // GE(a,b) = !LT(a,b): use LT logic but invert result
3134                        // CMP rn_lo, rm_lo (same as LT)
3135                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
3136
3137                        // SBCS rd, rn_hi, rm_hi (same as LT)
3138                        let rn_hi_bits = reg_to_bits(rn_hi);
3139                        let rm_hi_bits = reg_to_bits(rm_hi);
3140                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
3141                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
3142                        bytes.extend_from_slice(&hw1.to_le_bytes());
3143                        bytes.extend_from_slice(&hw2.to_le_bytes());
3144
3145                        // ITE GE; MOV rd, #1; MOV rd, #0 (GE is !LT)
3146                        bytes.extend_from_slice(&encode_setcond(0xA, rd_bits)); // GE = 0xA
3147                    }
3148
3149                    // Unsigned comparisons - same instruction sequence, different conditions
3150                    Condition::LO => {
3151                        // LO (unsigned LT): CMP lo, SBCS hi, check C=0
3152                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
3153                        let rn_hi_bits = reg_to_bits(rn_hi);
3154                        let rm_hi_bits = reg_to_bits(rm_hi);
3155                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
3156                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
3157                        bytes.extend_from_slice(&hw1.to_le_bytes());
3158                        bytes.extend_from_slice(&hw2.to_le_bytes());
3159                        bytes.extend_from_slice(&encode_setcond(0x3, rd_bits)); // LO = 0x3 (CC)
3160                    }
3161
3162                    Condition::HI => {
3163                        // HI (unsigned GT): swap operands and check LO
3164                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
3165                        let rm_hi_bits = reg_to_bits(rm_hi);
3166                        let rn_hi_bits = reg_to_bits(rn_hi);
3167                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
3168                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
3169                        bytes.extend_from_slice(&hw1.to_le_bytes());
3170                        bytes.extend_from_slice(&hw2.to_le_bytes());
3171                        bytes.extend_from_slice(&encode_setcond(0x3, rd_bits)); // LO = 0x3 (CC)
3172                    }
3173
3174                    Condition::LS => {
3175                        // LS (unsigned LE): !(a > b) = !(HI), so do HI and invert
3176                        bytes.extend_from_slice(&encode_cmp_reg(rm_lo, rn_lo));
3177                        let rm_hi_bits = reg_to_bits(rm_hi);
3178                        let rn_hi_bits = reg_to_bits(rn_hi);
3179                        let hw1: u16 = (0xEB70 | rm_hi_bits) as u16;
3180                        let hw2: u16 = ((rd_bits as u32) << 8 | rn_hi_bits) as u16;
3181                        bytes.extend_from_slice(&hw1.to_le_bytes());
3182                        bytes.extend_from_slice(&hw2.to_le_bytes());
3183                        bytes.extend_from_slice(&encode_setcond(0x2, rd_bits)); // HS = 0x2 (CS) = !LO
3184                    }
3185
3186                    Condition::HS => {
3187                        // HS (unsigned GE): !(a < b) = !(LO)
3188                        bytes.extend_from_slice(&encode_cmp_reg(rn_lo, rm_lo));
3189                        let rn_hi_bits = reg_to_bits(rn_hi);
3190                        let rm_hi_bits = reg_to_bits(rm_hi);
3191                        let hw1: u16 = (0xEB70 | rn_hi_bits) as u16;
3192                        let hw2: u16 = ((rd_bits as u32) << 8 | rm_hi_bits) as u16;
3193                        bytes.extend_from_slice(&hw1.to_le_bytes());
3194                        bytes.extend_from_slice(&hw2.to_le_bytes());
3195                        bytes.extend_from_slice(&encode_setcond(0x2, rd_bits)); // HS = 0x2 (CS) = !LO
3196                    }
3197                }
3198
3199                Ok(bytes)
3200            }
3201
3202            // I64SetCondZ: Test if i64 register pair is zero, result 0/1 in rd
3203            // ORR.W rd, rn_lo, rn_hi; CMP rd, #0; ITE EQ; MOV 1; MOV 0
3204            ArmOp::I64SetCondZ { rd, rn_lo, rn_hi } => {
3205                let rd_bits = reg_to_bits(rd);
3206                let rn_lo_bits = reg_to_bits(rn_lo);
3207                let rn_hi_bits = reg_to_bits(rn_hi);
3208                let mut bytes = Vec::new();
3209
3210                // ORR.W rd, rn_lo, rn_hi: EA40 rn_lo | 0000 rd 0000 rn_hi
3211                let hw1: u16 = (0xEA40 | rn_lo_bits) as u16;
3212                let hw2: u16 = ((rd_bits << 8) | rn_hi_bits) as u16;
3213                bytes.extend_from_slice(&hw1.to_le_bytes());
3214                bytes.extend_from_slice(&hw2.to_le_bytes());
3215
3216                // CMP rd, #0 — 16-bit form only for r0-r7 (3-bit rd field);
3217                // high registers take CMP.W (T2: F1B0|rn 0F00|imm8). This was
3218                // H-CODE-9: rd_bits<<8 overflowing the field compared the
3219                // WRONG register. Same hardening as the #311 SetCond fix.
3220                if rd_bits < 8 {
3221                    let cmp_instr: u16 = 0x2800 | ((rd_bits as u16) << 8);
3222                    bytes.extend_from_slice(&cmp_instr.to_le_bytes());
3223                } else {
3224                    let hw1: u16 = 0xF1B0 | (rd_bits as u16);
3225                    let hw2: u16 = 0x0F00;
3226                    bytes.extend_from_slice(&hw1.to_le_bytes());
3227                    bytes.extend_from_slice(&hw2.to_le_bytes());
3228                }
3229
3230                // ITE EQ; MOV rd, #1; MOV rd, #0 (32-bit MOV.W for rd >= R8,
3231                // #311 — see I64SetCond)
3232                let mask = 0xC_u16; // ITE EQ mask: firstcond[0]=0, mask=0xC
3233                let ite_instr: u16 = 0xBF00 | mask;
3234                bytes.extend_from_slice(&ite_instr.to_le_bytes());
3235                if rd_bits < 8 {
3236                    let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
3237                    let mov_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
3238                    bytes.extend_from_slice(&mov_one.to_le_bytes());
3239                    bytes.extend_from_slice(&mov_zero.to_le_bytes());
3240                } else {
3241                    for imm in [1u16, 0u16] {
3242                        let hw1: u16 = 0xF04F;
3243                        let hw2: u16 = ((rd_bits as u16) << 8) | imm;
3244                        bytes.extend_from_slice(&hw1.to_le_bytes());
3245                        bytes.extend_from_slice(&hw2.to_le_bytes());
3246                    }
3247                }
3248
3249                Ok(bytes)
3250            }
3251
3252            // I64Mul: 64-bit multiply using UMULL + MLA cross products
3253            // Formula: result = (a_lo * b_lo) + ((a_lo * b_hi + a_hi * b_lo) << 32)
3254            // Uses R12 as scratch register
3255            ArmOp::I64Mul {
3256                rd_lo,
3257                rd_hi,
3258                rn_lo,
3259                rn_hi,
3260                rm_lo,
3261                rm_hi,
3262            } => {
3263                let rd_lo_bits = reg_to_bits(rd_lo);
3264                let rd_hi_bits = reg_to_bits(rd_hi);
3265                let rn_lo_bits = reg_to_bits(rn_lo);
3266                let rn_hi_bits = reg_to_bits(rn_hi);
3267                let rm_lo_bits = reg_to_bits(rm_lo);
3268                let rm_hi_bits = reg_to_bits(rm_hi);
3269                let r12: u32 = 12; // IP scratch register
3270                let mut bytes = Vec::new();
3271
3272                // 1. MUL R12, rn_lo, rm_hi  (R12 = a_lo * b_hi)
3273                // Thumb-2 MUL: hw1=0xFB00|Rn, hw2=0xF000|(Rd<<8)|Rm
3274                let hw1: u16 = (0xFB00 | rn_lo_bits) as u16;
3275                let hw2: u16 = (0xF000 | (r12 << 8) | rm_hi_bits) as u16;
3276                bytes.extend_from_slice(&hw1.to_le_bytes());
3277                bytes.extend_from_slice(&hw2.to_le_bytes());
3278
3279                // 2. MLA R12, rn_hi, rm_lo, R12  (R12 += a_hi * b_lo)
3280                // Thumb-2 MLA: hw1=0xFB00|Rn, hw2=(Ra<<12)|(Rd<<8)|Rm
3281                let hw1: u16 = (0xFB00 | rn_hi_bits) as u16;
3282                let hw2: u16 = ((r12 << 12) | (r12 << 8) | rm_lo_bits) as u16;
3283                bytes.extend_from_slice(&hw1.to_le_bytes());
3284                bytes.extend_from_slice(&hw2.to_le_bytes());
3285
3286                // 3. UMULL rd_lo, rd_hi, rn_lo, rm_lo  (rd_lo:rd_hi = a_lo * b_lo)
3287                // Thumb-2 UMULL: hw1=0xFBA0|Rn, hw2=(RdLo<<12)|(RdHi<<8)|Rm
3288                let hw1: u16 = (0xFBA0 | rn_lo_bits) as u16;
3289                let hw2: u16 = ((rd_lo_bits << 12) | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3290                bytes.extend_from_slice(&hw1.to_le_bytes());
3291                bytes.extend_from_slice(&hw2.to_le_bytes());
3292
3293                // 4. ADD rd_hi, R12  (rd_hi += cross products)
3294                // 16-bit high reg ADD: 01000100 D Rm Rdn[2:0]
3295                let d_bit = (rd_hi_bits >> 3) & 1;
3296                let add_instr: u16 =
3297                    (0x4400 | (d_bit << 7) | (r12 << 3) | (rd_hi_bits & 0x7)) as u16;
3298                bytes.extend_from_slice(&add_instr.to_le_bytes());
3299
3300                Ok(bytes)
3301            }
3302
3303            // I64Shl: 64-bit shift left with branch for n<32 vs n>=32
3304            // rm_hi (R3) is used as temp register
3305            ArmOp::I64Shl {
3306                rd_lo,
3307                rd_hi,
3308                rn_lo,
3309                rn_hi,
3310                rm_lo,
3311                rm_hi,
3312            } => {
3313                let rd_lo_bits = reg_to_bits(rd_lo);
3314                let rd_hi_bits = reg_to_bits(rd_hi);
3315                let rn_lo_bits = reg_to_bits(rn_lo);
3316                let rn_hi_bits = reg_to_bits(rn_hi);
3317                let rm_lo_bits = reg_to_bits(rm_lo);
3318                let rm_hi_bits = reg_to_bits(rm_hi); // temp
3319                let mut bytes = Vec::new();
3320
3321                // AND.W rm_lo, rm_lo, #63  (mask shift amount to 6 bits)
3322                let hw1: u16 = (0xF000 | rm_lo_bits) as u16;
3323                let hw2: u16 = ((rm_lo_bits << 8) | 0x3F) as u16;
3324                bytes.extend_from_slice(&hw1.to_le_bytes());
3325                bytes.extend_from_slice(&hw2.to_le_bytes());
3326
3327                // SUBS.W rm_hi, rm_lo, #32  (rm_hi = n-32, sets flags)
3328                let hw1: u16 = (0xF1B0 | rm_lo_bits) as u16;
3329                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3330                bytes.extend_from_slice(&hw1.to_le_bytes());
3331                bytes.extend_from_slice(&hw2.to_le_bytes());
3332
3333                // BPL .large (branch if n >= 32, offset = +10 halfwords)
3334                let bpl: u16 = 0xD50A;
3335                bytes.extend_from_slice(&bpl.to_le_bytes());
3336
3337                // --- Small shift (n < 32) ---
3338                // RSB.W rm_hi, rm_lo, #32  (rm_hi = 32-n)
3339                let hw1: u16 = (0xF1C0 | rm_lo_bits) as u16;
3340                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3341                bytes.extend_from_slice(&hw1.to_le_bytes());
3342                bytes.extend_from_slice(&hw2.to_le_bytes());
3343
3344                // LSR.W rm_hi, rn_lo, rm_hi  (rm_hi = lo >> (32-n), overflow bits)
3345                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3346                let hw2: u16 = (0xF000 | (rm_hi_bits << 8) | rm_hi_bits) as u16;
3347                bytes.extend_from_slice(&hw1.to_le_bytes());
3348                bytes.extend_from_slice(&hw2.to_le_bytes());
3349
3350                // LSL.W rd_hi, rn_hi, rm_lo  (hi <<= n)
3351                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3352                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3353                bytes.extend_from_slice(&hw1.to_le_bytes());
3354                bytes.extend_from_slice(&hw2.to_le_bytes());
3355
3356                // ORR.W rd_hi, rd_hi, rm_hi  (hi |= overflow bits from lo)
3357                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3358                let hw2: u16 = ((rd_hi_bits << 8) | rm_hi_bits) as u16;
3359                bytes.extend_from_slice(&hw1.to_le_bytes());
3360                bytes.extend_from_slice(&hw2.to_le_bytes());
3361
3362                // LSL.W rd_lo, rn_lo, rm_lo  (lo <<= n)
3363                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3364                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_lo_bits) as u16;
3365                bytes.extend_from_slice(&hw1.to_le_bytes());
3366                bytes.extend_from_slice(&hw2.to_le_bytes());
3367
3368                // B .done (skip large shift: +2 halfwords)
3369                let b_done: u16 = 0xE002;
3370                bytes.extend_from_slice(&b_done.to_le_bytes());
3371
3372                // --- Large shift (n >= 32) ---
3373                // LSL.W rd_hi, rn_lo, rm_hi  (hi = lo << (n-32))
3374                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3375                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_hi_bits) as u16;
3376                bytes.extend_from_slice(&hw1.to_le_bytes());
3377                bytes.extend_from_slice(&hw2.to_le_bytes());
3378
3379                // MOV rd_lo, #0
3380                let mov_zero: u16 = 0x2000 | ((rd_lo_bits as u16) << 8);
3381                bytes.extend_from_slice(&mov_zero.to_le_bytes());
3382
3383                Ok(bytes) // Total: 38 bytes
3384            }
3385
3386            // I64ShrU: 64-bit logical shift right with branch for n<32 vs n>=32
3387            ArmOp::I64ShrU {
3388                rd_lo,
3389                rd_hi,
3390                rn_lo,
3391                rn_hi,
3392                rm_lo,
3393                rm_hi,
3394            } => {
3395                let rd_lo_bits = reg_to_bits(rd_lo);
3396                let rd_hi_bits = reg_to_bits(rd_hi);
3397                let rn_lo_bits = reg_to_bits(rn_lo);
3398                let rn_hi_bits = reg_to_bits(rn_hi);
3399                let rm_lo_bits = reg_to_bits(rm_lo);
3400                let rm_hi_bits = reg_to_bits(rm_hi); // temp
3401                let mut bytes = Vec::new();
3402
3403                // AND.W rm_lo, rm_lo, #63
3404                let hw1: u16 = (0xF000 | rm_lo_bits) as u16;
3405                let hw2: u16 = ((rm_lo_bits << 8) | 0x3F) as u16;
3406                bytes.extend_from_slice(&hw1.to_le_bytes());
3407                bytes.extend_from_slice(&hw2.to_le_bytes());
3408
3409                // SUBS.W rm_hi, rm_lo, #32
3410                let hw1: u16 = (0xF1B0 | rm_lo_bits) as u16;
3411                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3412                bytes.extend_from_slice(&hw1.to_le_bytes());
3413                bytes.extend_from_slice(&hw2.to_le_bytes());
3414
3415                // BPL .large (+10 halfwords)
3416                let bpl: u16 = 0xD50A;
3417                bytes.extend_from_slice(&bpl.to_le_bytes());
3418
3419                // --- Small shift (n < 32) ---
3420                // RSB.W rm_hi, rm_lo, #32  (rm_hi = 32-n)
3421                let hw1: u16 = (0xF1C0 | rm_lo_bits) as u16;
3422                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3423                bytes.extend_from_slice(&hw1.to_le_bytes());
3424                bytes.extend_from_slice(&hw2.to_le_bytes());
3425
3426                // LSL.W rm_hi, rn_hi, rm_hi  (rm_hi = hi << (32-n), bits flowing to lo)
3427                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3428                let hw2: u16 = (0xF000 | (rm_hi_bits << 8) | rm_hi_bits) as u16;
3429                bytes.extend_from_slice(&hw1.to_le_bytes());
3430                bytes.extend_from_slice(&hw2.to_le_bytes());
3431
3432                // LSR.W rd_lo, rn_lo, rm_lo  (lo >>= n)
3433                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3434                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_lo_bits) as u16;
3435                bytes.extend_from_slice(&hw1.to_le_bytes());
3436                bytes.extend_from_slice(&hw2.to_le_bytes());
3437
3438                // ORR.W rd_lo, rd_lo, rm_hi  (lo |= overflow from hi)
3439                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3440                let hw2: u16 = ((rd_lo_bits << 8) | rm_hi_bits) as u16;
3441                bytes.extend_from_slice(&hw1.to_le_bytes());
3442                bytes.extend_from_slice(&hw2.to_le_bytes());
3443
3444                // LSR.W rd_hi, rn_hi, rm_lo  (hi >>= n, logical)
3445                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3446                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3447                bytes.extend_from_slice(&hw1.to_le_bytes());
3448                bytes.extend_from_slice(&hw2.to_le_bytes());
3449
3450                // B .done (+2 halfwords)
3451                let b_done: u16 = 0xE002;
3452                bytes.extend_from_slice(&b_done.to_le_bytes());
3453
3454                // --- Large shift (n >= 32) ---
3455                // LSR.W rd_lo, rn_hi, rm_hi  (lo = hi >> (n-32))
3456                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3457                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_hi_bits) as u16;
3458                bytes.extend_from_slice(&hw1.to_le_bytes());
3459                bytes.extend_from_slice(&hw2.to_le_bytes());
3460
3461                // MOV rd_hi, #0
3462                let mov_zero: u16 = 0x2000 | ((rd_hi_bits as u16) << 8);
3463                bytes.extend_from_slice(&mov_zero.to_le_bytes());
3464
3465                Ok(bytes) // Total: 38 bytes
3466            }
3467
3468            // I64ShrS: 64-bit arithmetic shift right with branch for n<32 vs n>=32
3469            ArmOp::I64ShrS {
3470                rd_lo,
3471                rd_hi,
3472                rn_lo,
3473                rn_hi,
3474                rm_lo,
3475                rm_hi,
3476            } => {
3477                let rd_lo_bits = reg_to_bits(rd_lo);
3478                let rd_hi_bits = reg_to_bits(rd_hi);
3479                let rn_lo_bits = reg_to_bits(rn_lo);
3480                let rn_hi_bits = reg_to_bits(rn_hi);
3481                let rm_lo_bits = reg_to_bits(rm_lo);
3482                let rm_hi_bits = reg_to_bits(rm_hi); // temp
3483                let mut bytes = Vec::new();
3484
3485                // AND.W rm_lo, rm_lo, #63
3486                let hw1: u16 = (0xF000 | rm_lo_bits) as u16;
3487                let hw2: u16 = ((rm_lo_bits << 8) | 0x3F) as u16;
3488                bytes.extend_from_slice(&hw1.to_le_bytes());
3489                bytes.extend_from_slice(&hw2.to_le_bytes());
3490
3491                // SUBS.W rm_hi, rm_lo, #32
3492                let hw1: u16 = (0xF1B0 | rm_lo_bits) as u16;
3493                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3494                bytes.extend_from_slice(&hw1.to_le_bytes());
3495                bytes.extend_from_slice(&hw2.to_le_bytes());
3496
3497                // BPL .large (+10 halfwords)
3498                let bpl: u16 = 0xD50A;
3499                bytes.extend_from_slice(&bpl.to_le_bytes());
3500
3501                // --- Small shift (n < 32) ---
3502                // RSB.W rm_hi, rm_lo, #32
3503                let hw1: u16 = (0xF1C0 | rm_lo_bits) as u16;
3504                let hw2: u16 = ((rm_hi_bits << 8) | 0x20) as u16;
3505                bytes.extend_from_slice(&hw1.to_le_bytes());
3506                bytes.extend_from_slice(&hw2.to_le_bytes());
3507
3508                // LSL.W rm_hi, rn_hi, rm_hi  (rm_hi = hi << (32-n), bits flowing to lo)
3509                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3510                let hw2: u16 = (0xF000 | (rm_hi_bits << 8) | rm_hi_bits) as u16;
3511                bytes.extend_from_slice(&hw1.to_le_bytes());
3512                bytes.extend_from_slice(&hw2.to_le_bytes());
3513
3514                // LSR.W rd_lo, rn_lo, rm_lo  (lo >>= n, logical for lo word)
3515                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3516                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_lo_bits) as u16;
3517                bytes.extend_from_slice(&hw1.to_le_bytes());
3518                bytes.extend_from_slice(&hw2.to_le_bytes());
3519
3520                // ORR.W rd_lo, rd_lo, rm_hi  (lo |= overflow from hi)
3521                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3522                let hw2: u16 = ((rd_lo_bits << 8) | rm_hi_bits) as u16;
3523                bytes.extend_from_slice(&hw1.to_le_bytes());
3524                bytes.extend_from_slice(&hw2.to_le_bytes());
3525
3526                // ASR.W rd_hi, rn_hi, rm_lo  (hi >>= n, arithmetic/sign-extending)
3527                let hw1: u16 = (0xFA40 | rn_hi_bits) as u16;
3528                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | rm_lo_bits) as u16;
3529                bytes.extend_from_slice(&hw1.to_le_bytes());
3530                bytes.extend_from_slice(&hw2.to_le_bytes());
3531
3532                // B .done (+3 halfwords, large shift is 8 bytes)
3533                let b_done: u16 = 0xE003;
3534                bytes.extend_from_slice(&b_done.to_le_bytes());
3535
3536                // --- Large shift (n >= 32) ---
3537                // ASR.W rd_lo, rn_hi, rm_hi  (lo = hi >>> (n-32))
3538                let hw1: u16 = (0xFA40 | rn_hi_bits) as u16;
3539                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | rm_hi_bits) as u16;
3540                bytes.extend_from_slice(&hw1.to_le_bytes());
3541                bytes.extend_from_slice(&hw2.to_le_bytes());
3542
3543                // ASR.W rd_hi, rn_hi, #31  (hi = sign extension, all 0s or all 1s)
3544                // Thumb-2 ASR immediate: hw1=0xEA4F, hw2=imm3:Rd:imm2:10:Rm
3545                // imm5=31=11111 → imm3=111, imm2=11
3546                let hw1: u16 = 0xEA4F;
3547                let hw2: u16 = (0x7000 | (rd_hi_bits << 8) | 0x00E0 | rn_hi_bits) as u16;
3548                bytes.extend_from_slice(&hw1.to_le_bytes());
3549                bytes.extend_from_slice(&hw2.to_le_bytes());
3550
3551                Ok(bytes) // Total: 40 bytes
3552            }
3553
3554            // I64Rotl: 64-bit rotate left
3555            // For n < 32: new_hi = (hi << n) | (lo >> (32-n)), new_lo = (lo << n) | (hi >> (32-n))
3556            // For n >= 32: same formula but with lo/hi conceptually swapped, shift by (n-32)
3557            // Uses R4 (saved/restored) and R12 as scratch
3558            ArmOp::I64Rotl {
3559                rdlo,
3560                rdhi,
3561                rnlo,
3562                rnhi,
3563                shift,
3564            } => {
3565                let rd_lo_bits = reg_to_bits(rdlo);
3566                let rd_hi_bits = reg_to_bits(rdhi);
3567                let rn_lo_bits = reg_to_bits(rnlo);
3568                let rn_hi_bits = reg_to_bits(rnhi);
3569                let shift_bits = reg_to_bits(shift);
3570                let r12: u32 = 12; // IP scratch
3571                let r3: u32 = 3; // Scratch (high word of shift amount, unused)
3572                let r4: u32 = 4; // Scratch (saved/restored)
3573                let mut bytes = Vec::new();
3574
3575                // PUSH {R4}
3576                bytes.extend_from_slice(&0xB410u16.to_le_bytes());
3577
3578                // AND.W shift, shift, #63 (mask to 6 bits)
3579                let hw1: u16 = (0xF000 | shift_bits) as u16;
3580                let hw2: u16 = ((shift_bits << 8) | 0x3F) as u16;
3581                bytes.extend_from_slice(&hw1.to_le_bytes());
3582                bytes.extend_from_slice(&hw2.to_le_bytes());
3583
3584                // SUBS.W R3, shift, #32 (R3 = n-32, sets flags)
3585                let hw1: u16 = (0xF1B0 | shift_bits) as u16;
3586                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3587                bytes.extend_from_slice(&hw1.to_le_bytes());
3588                bytes.extend_from_slice(&hw2.to_le_bytes());
3589
3590                // BPL .large (branch if n >= 32, offset = +14 halfwords)
3591                let bpl: u16 = 0xD50E;
3592                bytes.extend_from_slice(&bpl.to_le_bytes());
3593
3594                // === Small rotation (n < 32) ===
3595                // RSB.W R3, shift, #32 (R3 = 32-n)
3596                let hw1: u16 = (0xF1C0 | shift_bits) as u16;
3597                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3598                bytes.extend_from_slice(&hw1.to_le_bytes());
3599                bytes.extend_from_slice(&hw2.to_le_bytes());
3600
3601                // LSR.W R4, rn_lo, R3 (R4 = lo >> (32-n), will go to new_hi)
3602                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3603                let hw2: u16 = (0xF000 | (r4 << 8) | r3) as u16;
3604                bytes.extend_from_slice(&hw1.to_le_bytes());
3605                bytes.extend_from_slice(&hw2.to_le_bytes());
3606
3607                // LSR.W R12, rn_hi, R3 (R12 = hi >> (32-n), will go to new_lo)
3608                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3609                let hw2: u16 = (0xF000 | (r12 << 8) | r3) as u16;
3610                bytes.extend_from_slice(&hw1.to_le_bytes());
3611                bytes.extend_from_slice(&hw2.to_le_bytes());
3612
3613                // LSL.W rd_hi, rn_hi, shift (rd_hi = hi << n)
3614                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3615                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | shift_bits) as u16;
3616                bytes.extend_from_slice(&hw1.to_le_bytes());
3617                bytes.extend_from_slice(&hw2.to_le_bytes());
3618
3619                // ORR.W rd_hi, rd_hi, R4 (rd_hi = (hi << n) | (lo >> (32-n)))
3620                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3621                let hw2: u16 = ((rd_hi_bits << 8) | r4) as u16;
3622                bytes.extend_from_slice(&hw1.to_le_bytes());
3623                bytes.extend_from_slice(&hw2.to_le_bytes());
3624
3625                // LSL.W rd_lo, rn_lo, shift (rd_lo = lo << n)
3626                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3627                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | shift_bits) as u16;
3628                bytes.extend_from_slice(&hw1.to_le_bytes());
3629                bytes.extend_from_slice(&hw2.to_le_bytes());
3630
3631                // ORR.W rd_lo, rd_lo, R12 (rd_lo = (lo << n) | (hi >> (32-n)))
3632                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3633                let hw2: u16 = ((rd_lo_bits << 8) | r12) as u16;
3634                bytes.extend_from_slice(&hw1.to_le_bytes());
3635                bytes.extend_from_slice(&hw2.to_le_bytes());
3636
3637                // B .done (skip large block, offset = +14 halfwords)
3638                let b_done: u16 = 0xE00E;
3639                bytes.extend_from_slice(&b_done.to_le_bytes());
3640
3641                // === Large rotation (n >= 32) ===
3642                // R3 already has n-32 from the SUBS
3643                // RSB.W R4, R3, #32 (R4 = 32-(n-32) = 64-n)
3644                let hw1: u16 = (0xF1C0 | r3) as u16;
3645                let hw2: u16 = ((r4 << 8) | 0x20) as u16;
3646                bytes.extend_from_slice(&hw1.to_le_bytes());
3647                bytes.extend_from_slice(&hw2.to_le_bytes());
3648
3649                // LSR.W R12, rn_hi, R4 (R12 = hi >> (64-n), goes to new_hi low bits)
3650                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3651                let hw2: u16 = (0xF000 | (r12 << 8) | r4) as u16;
3652                bytes.extend_from_slice(&hw1.to_le_bytes());
3653                bytes.extend_from_slice(&hw2.to_le_bytes());
3654
3655                // LSR.W R4, rn_lo, R4 (R4 = lo >> (64-n), goes to new_lo low bits)
3656                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3657                let hw2: u16 = (0xF000 | (r4 << 8) | r4) as u16;
3658                bytes.extend_from_slice(&hw1.to_le_bytes());
3659                bytes.extend_from_slice(&hw2.to_le_bytes());
3660
3661                // LSL.W shift, rn_lo, R3 (shift = lo << (n-32), new_hi high bits)
3662                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3663                let hw2: u16 = (0xF000 | (shift_bits << 8) | r3) as u16;
3664                bytes.extend_from_slice(&hw1.to_le_bytes());
3665                bytes.extend_from_slice(&hw2.to_le_bytes());
3666
3667                // ORR.W shift, shift, R12 (shift = (lo << (n-32)) | (hi >> (64-n)) = new_hi)
3668                let hw1: u16 = (0xEA40 | shift_bits) as u16;
3669                let hw2: u16 = ((shift_bits << 8) | r12) as u16;
3670                bytes.extend_from_slice(&hw1.to_le_bytes());
3671                bytes.extend_from_slice(&hw2.to_le_bytes());
3672
3673                // LSL.W rd_lo, rn_hi, R3 (rd_lo = hi << (n-32), new_lo high bits)
3674                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3675                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | r3) as u16;
3676                bytes.extend_from_slice(&hw1.to_le_bytes());
3677                bytes.extend_from_slice(&hw2.to_le_bytes());
3678
3679                // ORR.W rd_lo, rd_lo, R4 (rd_lo = (hi << (n-32)) | (lo >> (64-n)) = new_lo)
3680                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3681                let hw2: u16 = ((rd_lo_bits << 8) | r4) as u16;
3682                bytes.extend_from_slice(&hw1.to_le_bytes());
3683                bytes.extend_from_slice(&hw2.to_le_bytes());
3684
3685                // MOV rd_hi, shift (rd_hi = new_hi)
3686                let d_bit = (rd_hi_bits >> 3) & 1;
3687                let mov_instr: u16 =
3688                    (0x4600 | (d_bit << 7) | (shift_bits << 3) | (rd_hi_bits & 0x7)) as u16;
3689                bytes.extend_from_slice(&mov_instr.to_le_bytes());
3690
3691                // POP {R4}
3692                bytes.extend_from_slice(&0xBC10u16.to_le_bytes());
3693
3694                Ok(bytes) // Total: 74 bytes
3695            }
3696
3697            // I64Rotr: 64-bit rotate right
3698            // rotr(x, n) = rotl(x, 64-n)
3699            // For n < 32: new_lo = (lo >> n) | (hi << (32-n)), new_hi = (hi >> n) | (lo << (32-n))
3700            // For n >= 32: same formula but with lo/hi swapped, shift by (n-32)
3701            ArmOp::I64Rotr {
3702                rdlo,
3703                rdhi,
3704                rnlo,
3705                rnhi,
3706                shift,
3707            } => {
3708                let rd_lo_bits = reg_to_bits(rdlo);
3709                let rd_hi_bits = reg_to_bits(rdhi);
3710                let rn_lo_bits = reg_to_bits(rnlo);
3711                let rn_hi_bits = reg_to_bits(rnhi);
3712                let shift_bits = reg_to_bits(shift);
3713                let r12: u32 = 12;
3714                let r3: u32 = 3;
3715                let r4: u32 = 4;
3716                let mut bytes = Vec::new();
3717
3718                // PUSH {R4}
3719                bytes.extend_from_slice(&0xB410u16.to_le_bytes());
3720
3721                // AND.W shift, shift, #63
3722                let hw1: u16 = (0xF000 | shift_bits) as u16;
3723                let hw2: u16 = ((shift_bits << 8) | 0x3F) as u16;
3724                bytes.extend_from_slice(&hw1.to_le_bytes());
3725                bytes.extend_from_slice(&hw2.to_le_bytes());
3726
3727                // SUBS.W R3, shift, #32
3728                let hw1: u16 = (0xF1B0 | shift_bits) as u16;
3729                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3730                bytes.extend_from_slice(&hw1.to_le_bytes());
3731                bytes.extend_from_slice(&hw2.to_le_bytes());
3732
3733                // BPL .large (+14 halfwords)
3734                let bpl: u16 = 0xD50E;
3735                bytes.extend_from_slice(&bpl.to_le_bytes());
3736
3737                // === Small rotation (n < 32) ===
3738                // RSB.W R3, shift, #32 (R3 = 32-n)
3739                let hw1: u16 = (0xF1C0 | shift_bits) as u16;
3740                let hw2: u16 = ((r3 << 8) | 0x20) as u16;
3741                bytes.extend_from_slice(&hw1.to_le_bytes());
3742                bytes.extend_from_slice(&hw2.to_le_bytes());
3743
3744                // LSL.W R4, rn_hi, R3 (R4 = hi << (32-n), will go to new_lo)
3745                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3746                let hw2: u16 = (0xF000 | (r4 << 8) | r3) as u16;
3747                bytes.extend_from_slice(&hw1.to_le_bytes());
3748                bytes.extend_from_slice(&hw2.to_le_bytes());
3749
3750                // LSL.W R12, rn_lo, R3 (R12 = lo << (32-n), will go to new_hi)
3751                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3752                let hw2: u16 = (0xF000 | (r12 << 8) | r3) as u16;
3753                bytes.extend_from_slice(&hw1.to_le_bytes());
3754                bytes.extend_from_slice(&hw2.to_le_bytes());
3755
3756                // LSR.W rd_lo, rn_lo, shift (rd_lo = lo >> n)
3757                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3758                let hw2: u16 = (0xF000 | (rd_lo_bits << 8) | shift_bits) as u16;
3759                bytes.extend_from_slice(&hw1.to_le_bytes());
3760                bytes.extend_from_slice(&hw2.to_le_bytes());
3761
3762                // ORR.W rd_lo, rd_lo, R4 (rd_lo = (lo >> n) | (hi << (32-n)))
3763                let hw1: u16 = (0xEA40 | rd_lo_bits) as u16;
3764                let hw2: u16 = ((rd_lo_bits << 8) | r4) as u16;
3765                bytes.extend_from_slice(&hw1.to_le_bytes());
3766                bytes.extend_from_slice(&hw2.to_le_bytes());
3767
3768                // LSR.W rd_hi, rn_hi, shift (rd_hi = hi >> n)
3769                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3770                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | shift_bits) as u16;
3771                bytes.extend_from_slice(&hw1.to_le_bytes());
3772                bytes.extend_from_slice(&hw2.to_le_bytes());
3773
3774                // ORR.W rd_hi, rd_hi, R12 (rd_hi = (hi >> n) | (lo << (32-n)))
3775                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3776                let hw2: u16 = ((rd_hi_bits << 8) | r12) as u16;
3777                bytes.extend_from_slice(&hw1.to_le_bytes());
3778                bytes.extend_from_slice(&hw2.to_le_bytes());
3779
3780                // B .done (+14 halfwords)
3781                let b_done: u16 = 0xE00E;
3782                bytes.extend_from_slice(&b_done.to_le_bytes());
3783
3784                // === Large rotation (n >= 32) ===
3785                // RSB.W R4, R3, #32 (R4 = 64-n)
3786                let hw1: u16 = (0xF1C0 | r3) as u16;
3787                let hw2: u16 = ((r4 << 8) | 0x20) as u16;
3788                bytes.extend_from_slice(&hw1.to_le_bytes());
3789                bytes.extend_from_slice(&hw2.to_le_bytes());
3790
3791                // LSL.W R12, rn_lo, R4 (R12 = lo << (64-n), goes to new_lo low bits)
3792                let hw1: u16 = (0xFA00 | rn_lo_bits) as u16;
3793                let hw2: u16 = (0xF000 | (r12 << 8) | r4) as u16;
3794                bytes.extend_from_slice(&hw1.to_le_bytes());
3795                bytes.extend_from_slice(&hw2.to_le_bytes());
3796
3797                // LSL.W R4, rn_hi, R4 (R4 = hi << (64-n), goes to new_hi low bits)
3798                let hw1: u16 = (0xFA00 | rn_hi_bits) as u16;
3799                let hw2: u16 = (0xF000 | (r4 << 8) | r4) as u16;
3800                bytes.extend_from_slice(&hw1.to_le_bytes());
3801                bytes.extend_from_slice(&hw2.to_le_bytes());
3802
3803                // LSR.W shift, rn_hi, R3 (shift = hi >> (n-32), new_lo high bits)
3804                let hw1: u16 = (0xFA20 | rn_hi_bits) as u16;
3805                let hw2: u16 = (0xF000 | (shift_bits << 8) | r3) as u16;
3806                bytes.extend_from_slice(&hw1.to_le_bytes());
3807                bytes.extend_from_slice(&hw2.to_le_bytes());
3808
3809                // ORR.W shift, shift, R12 (shift = (hi >> (n-32)) | (lo << (64-n)) = new_lo)
3810                let hw1: u16 = (0xEA40 | shift_bits) as u16;
3811                let hw2: u16 = ((shift_bits << 8) | r12) as u16;
3812                bytes.extend_from_slice(&hw1.to_le_bytes());
3813                bytes.extend_from_slice(&hw2.to_le_bytes());
3814
3815                // LSR.W rd_hi, rn_lo, R3 (rd_hi = lo >> (n-32), new_hi high bits)
3816                let hw1: u16 = (0xFA20 | rn_lo_bits) as u16;
3817                let hw2: u16 = (0xF000 | (rd_hi_bits << 8) | r3) as u16;
3818                bytes.extend_from_slice(&hw1.to_le_bytes());
3819                bytes.extend_from_slice(&hw2.to_le_bytes());
3820
3821                // ORR.W rd_hi, rd_hi, R4 (rd_hi = (lo >> (n-32)) | (hi << (64-n)) = new_hi)
3822                let hw1: u16 = (0xEA40 | rd_hi_bits) as u16;
3823                let hw2: u16 = ((rd_hi_bits << 8) | r4) as u16;
3824                bytes.extend_from_slice(&hw1.to_le_bytes());
3825                bytes.extend_from_slice(&hw2.to_le_bytes());
3826
3827                // MOV rd_lo, shift (rd_lo = new_lo)
3828                let d_bit = (rd_lo_bits >> 3) & 1;
3829                let mov_instr: u16 =
3830                    (0x4600 | (d_bit << 7) | (shift_bits << 3) | (rd_lo_bits & 0x7)) as u16;
3831                bytes.extend_from_slice(&mov_instr.to_le_bytes());
3832
3833                // POP {R4}
3834                bytes.extend_from_slice(&0xBC10u16.to_le_bytes());
3835
3836                Ok(bytes) // Total: 74 bytes
3837            }
3838
3839            // I64Clz: Count leading zeros in 64-bit value
3840            // If hi != 0: result = CLZ(hi)
3841            // If hi == 0: result = 32 + CLZ(lo)
3842            //
3843            // Layout (using CMP+BNE approach for consistency):
3844            // 0: CMP.W rnhi, #0 (4 bytes)
3845            // 4: BEQ .hi_zero (2 bytes) - branch forward to offset 14
3846            // 6: CLZ.W rd, rnhi (4 bytes)
3847            // 10: B .done (2 bytes) - branch forward to offset 22
3848            // 12: NOP (2 bytes) - padding for alignment
3849            // 14: .hi_zero: CLZ.W rd, rnlo (4 bytes)
3850            // 18: ADD.W rd, rd, #32 (4 bytes)
3851            // 22: .done
3852            ArmOp::I64Clz { rd, rnlo, rnhi } => {
3853                let rd_bits = reg_to_bits(rd);
3854                let rn_lo_bits = reg_to_bits(rnlo);
3855                let rn_hi_bits = reg_to_bits(rnhi);
3856                let mut bytes = Vec::new();
3857
3858                // CMP.W rnhi, #0 (4 bytes at offset 0)
3859                let hw1: u16 = (0xF1B0 | rn_hi_bits) as u16;
3860                let hw2: u16 = 0x0F00;
3861                bytes.extend_from_slice(&hw1.to_le_bytes());
3862                bytes.extend_from_slice(&hw2.to_le_bytes());
3863
3864                // BEQ .hi_zero (2 bytes at offset 4)
3865                // PC = 4 + 4 = 8, target = 14, offset = 6, imm8 = 3
3866                let beq: u16 = 0xD003;
3867                bytes.extend_from_slice(&beq.to_le_bytes());
3868
3869                // CLZ.W rd, rnhi (4 bytes at offset 6)
3870                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3871                let hw1: u16 = (0xFAB0 | rn_hi_bits) as u16;
3872                let hw2: u16 = (0xF080 | (rd_bits << 8) | rn_hi_bits) as u16;
3873                bytes.extend_from_slice(&hw1.to_le_bytes());
3874                bytes.extend_from_slice(&hw2.to_le_bytes());
3875
3876                // B .done (2 bytes at offset 10)
3877                // PC = 10 + 4 = 14, target = 22, offset = 8, imm11 = 4
3878                let b_done: u16 = 0xE004;
3879                bytes.extend_from_slice(&b_done.to_le_bytes());
3880
3881                // NOP (2 bytes at offset 12) - padding
3882                bytes.extend_from_slice(&0xBF00u16.to_le_bytes());
3883
3884                // .hi_zero: (offset 14)
3885                // CLZ.W rd, rnlo (4 bytes)
3886                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3887                let hw1: u16 = (0xFAB0 | rn_lo_bits) as u16;
3888                let hw2: u16 = (0xF080 | (rd_bits << 8) | rn_lo_bits) as u16;
3889                bytes.extend_from_slice(&hw1.to_le_bytes());
3890                bytes.extend_from_slice(&hw2.to_le_bytes());
3891
3892                // ADD.W rd, rd, #32 (4 bytes at offset 18)
3893                let hw1: u16 = (0xF100 | rd_bits) as u16;
3894                let hw2: u16 = ((rd_bits << 8) | 0x20) as u16;
3895                bytes.extend_from_slice(&hw1.to_le_bytes());
3896                bytes.extend_from_slice(&hw2.to_le_bytes());
3897
3898                // .done: (offset 22)
3899                // i64.clz returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
3900                // MOVS Rn, #0: 0010 0 Rn 00000000
3901                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
3902                bytes.extend_from_slice(&mov0.to_le_bytes());
3903
3904                Ok(bytes)
3905            }
3906
3907            // I64Ctz: Count trailing zeros in 64-bit value
3908            // If lo != 0: result = CTZ(lo) = CLZ(RBIT(lo))
3909            // If lo == 0: result = 32 + CTZ(hi) = 32 + CLZ(RBIT(hi))
3910            //
3911            // Layout:
3912            // 0: CMP.W rnlo, #0 (4 bytes)
3913            // 4: BEQ .lo_zero (2 bytes) - branch to offset 18
3914            // 6: RBIT.W rd, rnlo (4 bytes)
3915            // 10: CLZ.W rd, rd (4 bytes)
3916            // 14: B .done (2 bytes) - branch to offset 30
3917            // 16: NOP (2 bytes) - padding
3918            // 18: .lo_zero: RBIT.W rd, rnhi (4 bytes)
3919            // 22: CLZ.W rd, rd (4 bytes)
3920            // 26: ADD.W rd, rd, #32 (4 bytes)
3921            // 30: .done
3922            ArmOp::I64Ctz { rd, rnlo, rnhi } => {
3923                let rd_bits = reg_to_bits(rd);
3924                let rn_lo_bits = reg_to_bits(rnlo);
3925                let rn_hi_bits = reg_to_bits(rnhi);
3926                let mut bytes = Vec::new();
3927
3928                // CMP.W rnlo, #0 (4 bytes at offset 0)
3929                let hw1: u16 = (0xF1B0 | rn_lo_bits) as u16;
3930                let hw2: u16 = 0x0F00;
3931                bytes.extend_from_slice(&hw1.to_le_bytes());
3932                bytes.extend_from_slice(&hw2.to_le_bytes());
3933
3934                // BEQ .lo_zero (2 bytes at offset 4)
3935                // PC = 4 + 4 = 8, target = 18, offset = 10, imm8 = 5
3936                let beq: u16 = 0xD005;
3937                bytes.extend_from_slice(&beq.to_le_bytes());
3938
3939                // RBIT.W rd, rnlo (4 bytes at offset 6)
3940                // RBIT T1: hw1 = 0xFA9<Rm>, hw2 = 0xF<Rd>A<Rm>
3941                let hw1: u16 = (0xFA90 | rn_lo_bits) as u16;
3942                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rn_lo_bits) as u16;
3943                bytes.extend_from_slice(&hw1.to_le_bytes());
3944                bytes.extend_from_slice(&hw2.to_le_bytes());
3945
3946                // CLZ.W rd, rd (4 bytes at offset 10)
3947                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3948                let hw1: u16 = (0xFAB0 | rd_bits) as u16;
3949                let hw2: u16 = (0xF080 | (rd_bits << 8) | rd_bits) as u16;
3950                bytes.extend_from_slice(&hw1.to_le_bytes());
3951                bytes.extend_from_slice(&hw2.to_le_bytes());
3952
3953                // B .done (2 bytes at offset 14)
3954                // PC = 14 + 4 = 18, target = 30, offset = 12, imm11 = 6
3955                let b_done: u16 = 0xE006;
3956                bytes.extend_from_slice(&b_done.to_le_bytes());
3957
3958                // NOP (2 bytes at offset 16) - padding
3959                bytes.extend_from_slice(&0xBF00u16.to_le_bytes());
3960
3961                // .lo_zero: (offset 18)
3962                // RBIT.W rd, rnhi (4 bytes)
3963                // RBIT T1: hw1 = 0xFA9<Rm>, hw2 = 0xF<Rd>A<Rm>
3964                let hw1: u16 = (0xFA90 | rn_hi_bits) as u16;
3965                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rn_hi_bits) as u16;
3966                bytes.extend_from_slice(&hw1.to_le_bytes());
3967                bytes.extend_from_slice(&hw2.to_le_bytes());
3968
3969                // CLZ.W rd, rd (4 bytes at offset 22)
3970                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3971                let hw1: u16 = (0xFAB0 | rd_bits) as u16;
3972                let hw2: u16 = (0xF080 | (rd_bits << 8) | rd_bits) as u16;
3973                bytes.extend_from_slice(&hw1.to_le_bytes());
3974                bytes.extend_from_slice(&hw2.to_le_bytes());
3975
3976                // ADD.W rd, rd, #32 (4 bytes at offset 26)
3977                let hw1: u16 = (0xF100 | rd_bits) as u16;
3978                let hw2: u16 = ((rd_bits << 8) | 0x20) as u16;
3979                bytes.extend_from_slice(&hw1.to_le_bytes());
3980                bytes.extend_from_slice(&hw2.to_le_bytes());
3981
3982                // .done: (offset 30)
3983                // i64.ctz returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
3984                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
3985                bytes.extend_from_slice(&mov0.to_le_bytes());
3986
3987                Ok(bytes)
3988            }
3989
3990            // I64Popcnt: Population count of 64-bit value
3991            // result = POPCNT(lo) + POPCNT(hi)
3992            // Using SIMD-style parallel bit counting algorithm
3993            ArmOp::I64Popcnt { rd, rnlo, rnhi } => {
3994                let rd_bits = reg_to_bits(rd);
3995                let rn_lo_bits = reg_to_bits(rnlo);
3996                let rn_hi_bits = reg_to_bits(rnhi);
3997                let r12: u32 = 12; // IP scratch
3998                let r3: u32 = 3; // Scratch for hi popcnt result
3999                let mut bytes = Vec::new();
4000
4001                // PUSH {R3, R4, R5} - save scratch registers
4002                bytes.extend_from_slice(&0xB438u16.to_le_bytes());
4003
4004                // Strategy: compute popcnt(lo) -> R4, popcnt(hi) -> R5, add them -> rd
4005                // Using lookup table approach for each byte would be too large
4006                // Using shift-and-add approach instead
4007
4008                // For simplicity and correctness, use the efficient parallel algorithm
4009                // but implement it as a series of inline operations
4010
4011                // MOV R4, rnlo
4012                let d_bit: u32 = 0; // R4 < 8, so high bit is 0
4013                let mov: u16 = (0x4600 | (d_bit << 7) | (rn_lo_bits << 3) | (4 & 0x7)) as u16;
4014                bytes.extend_from_slice(&mov.to_le_bytes());
4015
4016                // MOV R5, rnhi
4017                let d_bit: u32 = 0; // R5 < 8, so high bit is 0
4018                let mov: u16 = (0x4600 | (d_bit << 7) | (rn_hi_bits << 3) | (5 & 0x7)) as u16;
4019                bytes.extend_from_slice(&mov.to_le_bytes());
4020
4021                // --- POPCNT for R4 (lo word) ---
4022                // Step 1: x = x - ((x >> 1) & 0x55555555)
4023                // LSR.W R12, R4, #1
4024                let hw1: u16 = 0xEA4F;
4025                let hw2: u16 = ((r12 << 8) | 0x50 | 4) as u16;
4026                bytes.extend_from_slice(&hw1.to_le_bytes());
4027                bytes.extend_from_slice(&hw2.to_le_bytes());
4028
4029                // Load 0x55555555 into R3 using MOVW/MOVT
4030                // MOVW R3, #0x5555
4031                bytes.extend_from_slice(&0xF245u16.to_le_bytes());
4032                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
4033                // MOVT R3, #0x5555
4034                bytes.extend_from_slice(&0xF2C5u16.to_le_bytes());
4035                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
4036
4037                // AND.W R12, R12, R3
4038                let hw1: u16 = (0xEA00 | r12) as u16;
4039                let hw2: u16 = ((r12 << 8) | r3) as u16;
4040                bytes.extend_from_slice(&hw1.to_le_bytes());
4041                bytes.extend_from_slice(&hw2.to_le_bytes());
4042
4043                // SUB.W R4, R4, R12
4044                let hw1: u16 = (0xEBA0 | 4) as u16;
4045                let hw2: u16 = ((4 << 8) | r12) as u16;
4046                bytes.extend_from_slice(&hw1.to_le_bytes());
4047                bytes.extend_from_slice(&hw2.to_le_bytes());
4048
4049                // Step 2: x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
4050                // Load 0x33333333 into R3
4051                // MOVW R3, #0x3333
4052                bytes.extend_from_slice(&0xF243u16.to_le_bytes());
4053                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
4054                // MOVT R3, #0x3333
4055                bytes.extend_from_slice(&0xF2C3u16.to_le_bytes());
4056                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
4057
4058                // AND.W R12, R4, R3
4059                let hw1: u16 = (0xEA00 | 4) as u16;
4060                let hw2: u16 = ((r12 << 8) | r3) as u16;
4061                bytes.extend_from_slice(&hw1.to_le_bytes());
4062                bytes.extend_from_slice(&hw2.to_le_bytes());
4063
4064                // LSR.W R4, R4, #2
4065                let hw1: u16 = 0xEA4F;
4066                let hw2: u16 = ((4 << 8) | 0x90 | 4) as u16;
4067                bytes.extend_from_slice(&hw1.to_le_bytes());
4068                bytes.extend_from_slice(&hw2.to_le_bytes());
4069
4070                // AND.W R4, R4, R3
4071                let hw1: u16 = (0xEA00 | 4) as u16;
4072                let hw2: u16 = ((4 << 8) | r3) as u16;
4073                bytes.extend_from_slice(&hw1.to_le_bytes());
4074                bytes.extend_from_slice(&hw2.to_le_bytes());
4075
4076                // ADD.W R4, R4, R12
4077                let hw1: u16 = (0xEB00 | 4) as u16;
4078                let hw2: u16 = ((4 << 8) | r12) as u16;
4079                bytes.extend_from_slice(&hw1.to_le_bytes());
4080                bytes.extend_from_slice(&hw2.to_le_bytes());
4081
4082                // Step 3: x = (x + (x >> 4)) & 0x0F0F0F0F
4083                // LSR.W R12, R4, #4
4084                // hw2 = (imm3 << 12) | (Rd << 8) | (imm2 << 6) | (type << 4) | Rm
4085                // imm5=4=00100 → imm3=1, imm2=0, type=01(LSR)
4086                let hw1: u16 = 0xEA4F;
4087                let hw2: u16 = (0x1000 | (r12 << 8) | 0x10 | 4) as u16;
4088                bytes.extend_from_slice(&hw1.to_le_bytes());
4089                bytes.extend_from_slice(&hw2.to_le_bytes());
4090
4091                // ADD.W R4, R4, R12
4092                let hw1: u16 = (0xEB00 | 4) as u16;
4093                let hw2: u16 = ((4 << 8) | r12) as u16;
4094                bytes.extend_from_slice(&hw1.to_le_bytes());
4095                bytes.extend_from_slice(&hw2.to_le_bytes());
4096
4097                // Load 0x0F0F0F0F into R3
4098                // MOVW R3, #0x0F0F (imm4=0, i=1, imm3=7, imm8=0x0F)
4099                // hw1 = 11110 1 10 0100 0000 = 0xF640
4100                // hw2 = 0 111 0011 00001111 = 0x730F
4101                bytes.extend_from_slice(&0xF640u16.to_le_bytes());
4102                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
4103                // MOVT R3, #0x0F0F
4104                bytes.extend_from_slice(&0xF6C0u16.to_le_bytes());
4105                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
4106
4107                // AND.W R4, R4, R3
4108                let hw1: u16 = (0xEA00 | 4) as u16;
4109                let hw2: u16 = ((4 << 8) | r3) as u16;
4110                bytes.extend_from_slice(&hw1.to_le_bytes());
4111                bytes.extend_from_slice(&hw2.to_le_bytes());
4112
4113                // Step 4: x = x * 0x01010101 >> 24
4114                // Load 0x01010101 into R3
4115                // MOVW R3, #0x0101
4116                bytes.extend_from_slice(&0xF240u16.to_le_bytes());
4117                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
4118                // MOVT R3, #0x0101
4119                bytes.extend_from_slice(&0xF2C0u16.to_le_bytes());
4120                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
4121
4122                // MUL R4, R4, R3
4123                // MUL T2: hw1 = 0xFB00|Rn, hw2 = 0xF000|(Rd<<8)|Rm
4124                let hw1: u16 = (0xFB00 | 4) as u16;
4125                let hw2: u16 = (0xF000 | (4 << 8) | r3) as u16;
4126                bytes.extend_from_slice(&hw1.to_le_bytes());
4127                bytes.extend_from_slice(&hw2.to_le_bytes());
4128
4129                // LSR.W R4, R4, #24
4130                // imm5=24=11000 → imm3=6, imm2=0, type=01(LSR)
4131                let hw1: u16 = 0xEA4F;
4132                let hw2: u16 = (0x6000 | (4 << 8) | 0x10 | 4) as u16;
4133                bytes.extend_from_slice(&hw1.to_le_bytes());
4134                bytes.extend_from_slice(&hw2.to_le_bytes());
4135
4136                // --- POPCNT for R5 (hi word) - same algorithm ---
4137                // Step 1
4138                let hw1: u16 = 0xEA4F;
4139                let hw2: u16 = ((r12 << 8) | 0x50 | 5) as u16;
4140                bytes.extend_from_slice(&hw1.to_le_bytes());
4141                bytes.extend_from_slice(&hw2.to_le_bytes());
4142
4143                // Load 0x55555555 into R3
4144                bytes.extend_from_slice(&0xF245u16.to_le_bytes());
4145                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
4146                bytes.extend_from_slice(&0xF2C5u16.to_le_bytes());
4147                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
4148
4149                let hw1: u16 = (0xEA00 | r12) as u16;
4150                let hw2: u16 = ((r12 << 8) | r3) as u16;
4151                bytes.extend_from_slice(&hw1.to_le_bytes());
4152                bytes.extend_from_slice(&hw2.to_le_bytes());
4153
4154                let hw1: u16 = (0xEBA0 | 5) as u16;
4155                let hw2: u16 = ((5 << 8) | r12) as u16;
4156                bytes.extend_from_slice(&hw1.to_le_bytes());
4157                bytes.extend_from_slice(&hw2.to_le_bytes());
4158
4159                // Step 2
4160                bytes.extend_from_slice(&0xF243u16.to_le_bytes());
4161                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
4162                bytes.extend_from_slice(&0xF2C3u16.to_le_bytes());
4163                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
4164
4165                let hw1: u16 = (0xEA00 | 5) as u16;
4166                let hw2: u16 = ((r12 << 8) | r3) as u16;
4167                bytes.extend_from_slice(&hw1.to_le_bytes());
4168                bytes.extend_from_slice(&hw2.to_le_bytes());
4169
4170                let hw1: u16 = 0xEA4F;
4171                let hw2: u16 = ((5 << 8) | 0x90 | 5) as u16;
4172                bytes.extend_from_slice(&hw1.to_le_bytes());
4173                bytes.extend_from_slice(&hw2.to_le_bytes());
4174
4175                let hw1: u16 = (0xEA00 | 5) as u16;
4176                let hw2: u16 = ((5 << 8) | r3) as u16;
4177                bytes.extend_from_slice(&hw1.to_le_bytes());
4178                bytes.extend_from_slice(&hw2.to_le_bytes());
4179
4180                let hw1: u16 = (0xEB00 | 5) as u16;
4181                let hw2: u16 = ((5 << 8) | r12) as u16;
4182                bytes.extend_from_slice(&hw1.to_le_bytes());
4183                bytes.extend_from_slice(&hw2.to_le_bytes());
4184
4185                // Step 3: LSR.W R12, R5, #4
4186                // imm5=4=00100 → imm3=1, imm2=0, type=01(LSR)
4187                let hw1: u16 = 0xEA4F;
4188                let hw2: u16 = (0x1000 | (r12 << 8) | 0x10 | 5) as u16;
4189                bytes.extend_from_slice(&hw1.to_le_bytes());
4190                bytes.extend_from_slice(&hw2.to_le_bytes());
4191
4192                let hw1: u16 = (0xEB00 | 5) as u16;
4193                let hw2: u16 = ((5 << 8) | r12) as u16;
4194                bytes.extend_from_slice(&hw1.to_le_bytes());
4195                bytes.extend_from_slice(&hw2.to_le_bytes());
4196
4197                // Load 0x0F0F0F0F into R3 (for hi-word)
4198                bytes.extend_from_slice(&0xF640u16.to_le_bytes());
4199                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
4200                bytes.extend_from_slice(&0xF6C0u16.to_le_bytes());
4201                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
4202
4203                let hw1: u16 = (0xEA00 | 5) as u16;
4204                let hw2: u16 = ((5 << 8) | r3) as u16;
4205                bytes.extend_from_slice(&hw1.to_le_bytes());
4206                bytes.extend_from_slice(&hw2.to_le_bytes());
4207
4208                // Step 4
4209                bytes.extend_from_slice(&0xF240u16.to_le_bytes());
4210                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
4211                bytes.extend_from_slice(&0xF2C0u16.to_le_bytes());
4212                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
4213
4214                // MUL R5, R5, R3
4215                // MUL T2: hw1 = 0xFB00|Rn, hw2 = 0xF000|(Rd<<8)|Rm
4216                let hw1: u16 = (0xFB00 | 5) as u16;
4217                let hw2: u16 = (0xF000 | (5 << 8) | r3) as u16;
4218                bytes.extend_from_slice(&hw1.to_le_bytes());
4219                bytes.extend_from_slice(&hw2.to_le_bytes());
4220
4221                // LSR.W R5, R5, #24
4222                // imm5=24=11000 → imm3=6, imm2=0, type=01(LSR)
4223                let hw1: u16 = 0xEA4F;
4224                let hw2: u16 = (0x6000 | (5 << 8) | 0x10 | 5) as u16;
4225                bytes.extend_from_slice(&hw1.to_le_bytes());
4226                bytes.extend_from_slice(&hw2.to_le_bytes());
4227
4228                // ADD rd, R4, R5 (combine lo and hi counts)
4229                // ADDS Rd, Rn, Rm (T1): 0001 100 Rm Rn Rd = 0x1800 | (Rm<<6) | (Rn<<3) | Rd
4230                let rd_bits_u16 = rd_bits as u16;
4231                let instr: u16 = 0x1800 | (5 << 6) | (4 << 3) | rd_bits_u16;
4232                bytes.extend_from_slice(&instr.to_le_bytes());
4233
4234                // POP {R3, R4, R5}
4235                bytes.extend_from_slice(&0xBC38u16.to_le_bytes());
4236
4237                // i64.popcnt returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
4238                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
4239                bytes.extend_from_slice(&mov0.to_le_bytes());
4240
4241                Ok(bytes)
4242            }
4243
4244            // I64Extend8S: Sign-extend low 8 bits to 64 bits
4245            // Result: rdlo = sign_extend_8(rnlo), rdhi = rdlo >> 31
4246            ArmOp::I64Extend8S { rdlo, rdhi, rnlo } => {
4247                let rdlo_bits = reg_to_bits(rdlo);
4248                let rdhi_bits = reg_to_bits(rdhi);
4249                let rnlo_bits = reg_to_bits(rnlo);
4250                let mut bytes = Vec::new();
4251
4252                // SXTB.W rdlo, rnlo (sign-extend byte to 32-bit)
4253                // SXTB T2: hw1 = 0xFA4F, hw2 = 0xF0<Rd><Rm>
4254                let hw1: u16 = 0xFA4F_u16;
4255                let hw2: u16 = (0xF080 | (rdlo_bits << 8) | rnlo_bits) as u16;
4256                bytes.extend_from_slice(&hw1.to_le_bytes());
4257                bytes.extend_from_slice(&hw2.to_le_bytes());
4258
4259                // ASR.W rdhi, rdlo, #31 (sign-extend to high word)
4260                // ASR (immediate): hw1 = 0xEA4F, hw2 = imm3:Rd:imm2:type:Rm
4261                // For imm5=31: imm3=111, imm2=11, type=10 (ASR)
4262                // hw2 = (7 << 12) | (rdhi << 8) | (3 << 6) | (2 << 4) | rdlo
4263                let hw1: u16 = 0xEA4F;
4264                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rdlo_bits) as u16;
4265                bytes.extend_from_slice(&hw1.to_le_bytes());
4266                bytes.extend_from_slice(&hw2.to_le_bytes());
4267
4268                Ok(bytes)
4269            }
4270
4271            // I64Extend16S: Sign-extend low 16 bits to 64 bits
4272            // Result: rdlo = sign_extend_16(rnlo), rdhi = rdlo >> 31
4273            ArmOp::I64Extend16S { rdlo, rdhi, rnlo } => {
4274                let rdlo_bits = reg_to_bits(rdlo);
4275                let rdhi_bits = reg_to_bits(rdhi);
4276                let rnlo_bits = reg_to_bits(rnlo);
4277                let mut bytes = Vec::new();
4278
4279                // SXTH.W rdlo, rnlo (sign-extend halfword to 32-bit)
4280                // SXTH T2: hw1 = 0xFA0F, hw2 = 0xF0<Rd><Rm>
4281                let hw1: u16 = 0xFA0F_u16;
4282                let hw2: u16 = (0xF080 | (rdlo_bits << 8) | rnlo_bits) as u16;
4283                bytes.extend_from_slice(&hw1.to_le_bytes());
4284                bytes.extend_from_slice(&hw2.to_le_bytes());
4285
4286                // ASR.W rdhi, rdlo, #31 (sign-extend to high word)
4287                let hw1: u16 = 0xEA4F;
4288                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rdlo_bits) as u16;
4289                bytes.extend_from_slice(&hw1.to_le_bytes());
4290                bytes.extend_from_slice(&hw2.to_le_bytes());
4291
4292                Ok(bytes)
4293            }
4294
4295            // I64Extend32S: Sign-extend low 32 bits to 64 bits
4296            // Result: rdlo = rnlo, rdhi = rnlo >> 31
4297            ArmOp::I64Extend32S { rdlo, rdhi, rnlo } => {
4298                let rdlo_bits = reg_to_bits(rdlo);
4299                let rdhi_bits = reg_to_bits(rdhi);
4300                let rnlo_bits = reg_to_bits(rnlo);
4301                let mut bytes = Vec::new();
4302
4303                // MOV rdlo, rnlo (if different)
4304                if rdlo_bits != rnlo_bits {
4305                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4306                    let d_bit = ((rdlo_bits >> 3) & 1) as u16;
4307                    let mov: u16 = 0x4600
4308                        | (d_bit << 7)
4309                        | ((rnlo_bits as u16) << 3)
4310                        | ((rdlo_bits & 0x7) as u16);
4311                    bytes.extend_from_slice(&mov.to_le_bytes());
4312                }
4313
4314                // ASR.W rdhi, rnlo, #31 (sign-extend to high word)
4315                let hw1: u16 = 0xEA4F;
4316                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rnlo_bits) as u16;
4317                bytes.extend_from_slice(&hw1.to_le_bytes());
4318                bytes.extend_from_slice(&hw2.to_le_bytes());
4319
4320                Ok(bytes)
4321            }
4322
4323            // SelectMove: IT <cond>; MOV{cond} rd, rm
4324            // Conditional move: only execute MOV if condition is true
4325            ArmOp::SelectMove { rd, rm, cond } => {
4326                let rd_bits = reg_to_bits(rd) as u16;
4327                let rm_bits = reg_to_bits(rm) as u16;
4328
4329                // Condition code encoding for IT block
4330                use synth_synthesis::Condition;
4331                let cond_bits: u16 = match cond {
4332                    Condition::EQ => 0x0, // Equal
4333                    Condition::NE => 0x1, // Not equal
4334                    Condition::HS => 0x2, // Higher or same (unsigned >=)
4335                    Condition::LO => 0x3, // Lower (unsigned <)
4336                    Condition::HI => 0x8, // Higher (unsigned >)
4337                    Condition::LS => 0x9, // Lower or same (unsigned <=)
4338                    Condition::GE => 0xA, // Greater or equal (signed)
4339                    Condition::LT => 0xB, // Less than (signed)
4340                    Condition::GT => 0xC, // Greater than (signed)
4341                    Condition::LE => 0xD, // Less or equal (signed)
4342                };
4343
4344                // IT <cond>: single Then block (mask = 0x8 for T only)
4345                // IT instruction: 1011 1111 firstcond mask
4346                let it_instr: u16 = 0xBF00 | (cond_bits << 4) | 0x8;
4347
4348                // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4349                // This MOV will only execute if condition is true due to IT block
4350                let d_bit = (rd_bits >> 3) & 1;
4351                let mov_instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
4352
4353                // Emit: IT <cond>, MOV rd, rm
4354                let mut bytes = it_instr.to_le_bytes().to_vec();
4355                bytes.extend_from_slice(&mov_instr.to_le_bytes());
4356                Ok(bytes)
4357            }
4358
4359            // Popcnt: Population count (count set bits)
4360            // ARM Cortex-M has no native POPCNT, so we implement the bit manipulation algorithm:
4361            // x = x - ((x >> 1) & 0x55555555);
4362            // x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
4363            // x = (x + (x >> 4)) & 0x0F0F0F0F;
4364            // x = x + (x >> 8);
4365            // x = x + (x >> 16);
4366            // return x & 0x3F;
4367            //
4368            // Uses rd as working register and R12 as scratch for constants
4369            ArmOp::Popcnt { rd, rm } => {
4370                let mut bytes = Vec::new();
4371
4372                // First, move rm to rd if they're different
4373                if rd != rm {
4374                    let rd_bits = reg_to_bits(rd) as u16;
4375                    let rm_bits = reg_to_bits(rm) as u16;
4376                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4377                    let d_bit = (rd_bits >> 3) & 1;
4378                    let mov_instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
4379                    bytes.extend_from_slice(&mov_instr.to_le_bytes());
4380                }
4381
4382                // Step 1: x = x - ((x >> 1) & 0x55555555)
4383                // Load 0x55555555 into R12
4384                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x5555)?);
4385                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x5555)?);
4386
4387                // R12_temp = rd >> 1
4388                // We need a second scratch register. Use R11.
4389                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 1)?);
4390
4391                // R11 = R11 & R12 (R11 = (x >> 1) & 0x55555555)
4392                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(11, 11, 12)?);
4393
4394                // rd = rd - R11
4395                bytes.extend_from_slice(&self.encode_thumb32_sub_reg_raw(
4396                    reg_to_bits(rd),
4397                    reg_to_bits(rd),
4398                    11,
4399                )?);
4400
4401                // Step 2: x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
4402                // Load 0x33333333 into R12
4403                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x3333)?);
4404                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x3333)?);
4405
4406                // R11 = rd & R12
4407                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4408                    11,
4409                    reg_to_bits(rd),
4410                    12,
4411                )?);
4412
4413                // rd = rd >> 2
4414                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(
4415                    reg_to_bits(rd),
4416                    reg_to_bits(rd),
4417                    2,
4418                )?);
4419
4420                // rd = rd & R12
4421                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4422                    reg_to_bits(rd),
4423                    reg_to_bits(rd),
4424                    12,
4425                )?);
4426
4427                // rd = rd + R11
4428                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4429                    reg_to_bits(rd),
4430                    reg_to_bits(rd),
4431                    11,
4432                )?);
4433
4434                // Step 3: x = (x + (x >> 4)) & 0x0F0F0F0F
4435                // R11 = rd >> 4
4436                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 4)?);
4437
4438                // rd = rd + R11
4439                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4440                    reg_to_bits(rd),
4441                    reg_to_bits(rd),
4442                    11,
4443                )?);
4444
4445                // Load 0x0F0F0F0F into R12
4446                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x0F0F)?);
4447                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x0F0F)?);
4448
4449                // rd = rd & R12
4450                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4451                    reg_to_bits(rd),
4452                    reg_to_bits(rd),
4453                    12,
4454                )?);
4455
4456                // Step 4: x = x + (x >> 8)
4457                // R11 = rd >> 8
4458                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 8)?);
4459
4460                // rd = rd + R11
4461                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4462                    reg_to_bits(rd),
4463                    reg_to_bits(rd),
4464                    11,
4465                )?);
4466
4467                // Step 5: x = x + (x >> 16)
4468                // R11 = rd >> 16
4469                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 16)?);
4470
4471                // rd = rd + R11
4472                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4473                    reg_to_bits(rd),
4474                    reg_to_bits(rd),
4475                    11,
4476                )?);
4477
4478                // Step 6: return x & 0x3F
4479                // AND with 0x3F (small immediate, can use BIC or AND with immediate)
4480                bytes.extend_from_slice(&self.encode_thumb32_and_imm_raw(
4481                    reg_to_bits(rd),
4482                    reg_to_bits(rd),
4483                    0x3F,
4484                )?);
4485
4486                Ok(bytes)
4487            }
4488
4489            // I64DivU: 64-bit unsigned division using binary long division
4490            // Input: R0:R1 = dividend, R2:R3 = divisor
4491            // Output: R0:R1 = quotient
4492            // Uses: R4-R7, R12 as loop counter (avoid R8 for Renode compatibility)
4493            ArmOp::I64DivU {
4494                rdlo: _,
4495                rdhi: _,
4496                rnlo: _,
4497                rnhi: _,
4498                rmlo: _,
4499                rmhi: _,
4500            } => {
4501                let mut bytes = Vec::new();
4502
4503                // PUSH {R4-R7} - save scratch registers (NO LR — this is inline code)
4504                // 16-bit PUSH: 1011 010 M rrrrrrrr where M=0 (no LR), r=R4-R7 = 0xF0
4505                // Encoding: 1011 0100 1111 0000 = 0xB4F0
4506                bytes.extend_from_slice(&0xB4F0u16.to_le_bytes());
4507
4508                // Initialize quotient (R4:R5) = 0
4509                bytes.extend_from_slice(&0x2400u16.to_le_bytes()); // MOV R4, #0
4510                bytes.extend_from_slice(&0x2500u16.to_le_bytes()); // MOV R5, #0
4511
4512                // Initialize remainder (R6:R7) = 0
4513                bytes.extend_from_slice(&0x2600u16.to_le_bytes()); // MOV R6, #0
4514                bytes.extend_from_slice(&0x2700u16.to_le_bytes()); // MOV R7, #0
4515
4516                // Initialize loop counter R12 = 64 (use R12 scratch instead of R8)
4517                // MOV.W R12, #64: F04F 0C40
4518                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4519                bytes.extend_from_slice(&0x0C40u16.to_le_bytes());
4520
4521                // Loop start
4522                let loop_start = bytes.len();
4523
4524                // === Loop body: process one bit ===
4525
4526                // 1. Shift quotient R4:R5 left by 1
4527                // LSLS R5, R5, #1 (16-bit: 0000 0010 1010 1101 = 0x006D -> actually 0x002D for LSL R5,R5,#1)
4528                // LSL Rd, Rm, #imm5: 000 00 imm5 Rm Rd = 000 00 00001 101 101 = 0x006D
4529                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4530                // Get carry from R4 into R5: ORR R5, R5, R4 LSR #31
4531                // Thumb-2 ORR with shifted register: EA45 75D4 = ORR.W R5, R5, R4, LSR #31
4532                // 11101010 010 S Rn | 0 imm3 Rd imm2 type Rm
4533                // type=01 (LSR), imm5=31 (imm3=111, imm2=11)
4534                bytes.extend_from_slice(&0xEA45u16.to_le_bytes());
4535                bytes.extend_from_slice(&0x75D4u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4536                // LSLS R4, R4, #1: 000 00 00001 100 100 = 0x0064
4537                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4538
4539                // 2. Shift remainder R6:R7 left by 1, OR in MSB of dividend R1
4540                // LSLS R7, R7, #1
4541                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4542                // ORR.W R7, R7, R6, LSR #31
4543                bytes.extend_from_slice(&0xEA47u16.to_le_bytes());
4544                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4545                // LSLS R6, R6, #1
4546                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4547                // ORR.W R6, R6, R1, LSR #31 (bring in MSB of dividend high)
4548                bytes.extend_from_slice(&0xEA46u16.to_le_bytes());
4549                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4550
4551                // 3. Shift dividend R0:R1 left by 1
4552                // LSLS R1, R1, #1
4553                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4554                // ORR.W R1, R1, R0, LSR #31
4555                bytes.extend_from_slice(&0xEA41u16.to_le_bytes());
4556                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4557                // LSLS R0, R0, #1
4558                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4559
4560                // 4. Compare remainder >= divisor (64-bit unsigned comparison)
4561                // Compare high words first: CMP R7, R3
4562                // CMP Rn, Rm encoding: 0x4280 | (Rm << 3) | Rn
4563                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3 (16-bit)
4564                // BHI means R7 > R3 (unsigned) - definitely subtract
4565                // BLO means R7 < R3 - definitely don't subtract
4566                // BEQ means need to check low words
4567
4568                // If high > divisor high: branch to subtract (forward +offset)
4569                // BHI.N +6 (skip CMP, skip BLO, do subtract)
4570                // BHI: 1101 1000 offset8 where cond=1000 (HI)
4571                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4 (to subtract block)
4572
4573                // If high < divisor high: branch past subtract
4574                // BLO.N +10 (skip to decrement)
4575                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BLO/BCC +12 (past subtract)
4576
4577                // High words equal, compare low: CMP R6, R2
4578                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2 (16-bit)
4579                // BLO/BCC past subtract (skip SUBS+SBC.W+ORR.W = 10 bytes = 4 halfwords from PC+4)
4580                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords (past subtract)
4581
4582                // === Subtract block: remainder -= divisor, quotient |= 1 ===
4583                // SUBS R6, R6, R2
4584                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2 (16-bit)
4585                // SBC R7, R7, R3 (with borrow)
4586                // Thumb-2 SBC.W: EB67 0703 = SBC.W R7, R7, R3
4587                bytes.extend_from_slice(&0xEB67u16.to_le_bytes());
4588                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4589                // ORR R4, R4, #1 (set bit 0 of quotient low)
4590                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4591                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4592
4593                // === Decrement counter and loop ===
4594                // SUBS.W R12, R12, #1 (decrement loop counter)
4595                // SUBS.W R12, R12, #1: F1BC 0C01
4596                bytes.extend_from_slice(&0xF1BCu16.to_le_bytes());
4597                bytes.extend_from_slice(&0x0C01u16.to_le_bytes());
4598
4599                // BNE back to loop_start
4600                let branch_offset_bytes = bytes.len() - loop_start + 4; // +4 for pipeline
4601                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4602                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4603                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4604
4605                // === Loop done, move quotient to R0:R1 ===
4606                bytes.extend_from_slice(&0x4620u16.to_le_bytes()); // MOV R0, R4
4607                bytes.extend_from_slice(&0x4629u16.to_le_bytes()); // MOV R1, R5
4608
4609                // POP {R4-R7} - restore scratch registers (NO PC — inline code continues)
4610                // 16-bit POP: 1011 110 P rrrrrrrr where P=0 (no PC), r=R4-R7 = 0xF0
4611                // Encoding: 1011 1100 1111 0000 = 0xBCF0
4612                bytes.extend_from_slice(&0xBCF0u16.to_le_bytes());
4613
4614                Ok(bytes)
4615            }
4616
4617            // I64DivS: 64-bit signed division
4618            // Converts to unsigned, divides, then applies sign
4619            // Input: R0:R1 = dividend (signed), R2:R3 = divisor (signed)
4620            // Output: R0:R1 = quotient (signed)
4621            ArmOp::I64DivS {
4622                rdlo: _,
4623                rdhi: _,
4624                rnlo: _,
4625                rnhi: _,
4626                rmlo: _,
4627                rmhi: _,
4628            } => {
4629                let mut bytes = Vec::new();
4630
4631                // PUSH {R4-R11} - save scratch registers (NO LR — inline code)
4632                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4633                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4634
4635                // Save result sign in R9: R9 = R1 XOR R3 (sign bit = MSB)
4636                // EOR.W R9, R1, R3
4637                bytes.extend_from_slice(&0xEA81u16.to_le_bytes());
4638                bytes.extend_from_slice(&0x0903u16.to_le_bytes());
4639
4640                // If dividend negative (R1 MSB set), negate it
4641                // TST R1, R1 (check sign)
4642                bytes.extend_from_slice(&0x4209u16.to_le_bytes()); // TST R1, R1
4643                // BPL skip_neg_dividend (+10 bytes = 5 halfwords)
4644                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4645
4646                // Negate R0:R1 (64-bit): RSBS R0, R0, #0; SBC R1, R1, R1 LSL #1
4647                // Actually: MVN R0, R0; MVN R1, R1; ADDS R0, R0, #1; ADC R1, R1, #0
4648                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4649                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4650                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4651                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4652                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4653
4654                // If divisor negative (R3 MSB set), negate it
4655                bytes.extend_from_slice(&0x421Bu16.to_le_bytes()); // TST R3, R3
4656                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4657
4658                // Negate R2:R3
4659                bytes.extend_from_slice(&0x43D2u16.to_le_bytes()); // MVNS R2, R2
4660                bytes.extend_from_slice(&0x43DBu16.to_le_bytes()); // MVNS R3, R3
4661                bytes.extend_from_slice(&0x1C52u16.to_le_bytes()); // ADDS R2, R2, #1
4662                bytes.extend_from_slice(&0xF143u16.to_le_bytes()); // ADC.W R3, R3, #0
4663                bytes.extend_from_slice(&0x0300u16.to_le_bytes());
4664
4665                // === Now do unsigned division (same as I64DivU) ===
4666                // Initialize quotient (R4:R5) = 0
4667                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4668                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4669                // Initialize remainder (R6:R7) = 0
4670                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4671                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4672                // Initialize loop counter R8 = 64
4673                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4674                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4675
4676                let loop_start = bytes.len();
4677
4678                // Shift quotient left
4679                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4680                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4681                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4682                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4683
4684                // Shift remainder left, OR in MSB of dividend
4685                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4686                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4687                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4688                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4689                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4690                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4691
4692                // Shift dividend left
4693                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4694                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4695                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4696                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4697
4698                // Compare and conditionally subtract
4699                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4700                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4701                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4702                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4703                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4704
4705                // Subtract and set quotient bit
4706                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4707                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4708                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4709                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4710                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4711
4712                // Decrement and loop
4713                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4714                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4715
4716                let branch_offset_bytes = bytes.len() - loop_start + 4;
4717                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4718                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4719                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4720
4721                // Move quotient to R0:R1
4722                bytes.extend_from_slice(&0x4620u16.to_le_bytes()); // MOV R0, R4
4723                bytes.extend_from_slice(&0x4629u16.to_le_bytes()); // MOV R1, R5
4724
4725                // If result should be negative (R9 MSB set), negate R0:R1
4726                bytes.extend_from_slice(&0xF1B9u16.to_le_bytes()); // TST.W R9, R9 (check MSB)
4727                bytes.extend_from_slice(&0x0F00u16.to_le_bytes());
4728                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8 (skip negation)
4729
4730                // Negate result R0:R1
4731                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4732                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4733                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4734                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4735                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4736
4737                // POP {R4-R11} - restore scratch registers (NO PC — inline code continues)
4738                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4739                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4740
4741                Ok(bytes)
4742            }
4743
4744            // I64RemU: 64-bit unsigned remainder using binary long division
4745            // Same algorithm as I64DivU but returns remainder instead of quotient
4746            // Input: R0:R1 = dividend, R2:R3 = divisor
4747            // Output: R0:R1 = remainder
4748            ArmOp::I64RemU {
4749                rdlo: _,
4750                rdhi: _,
4751                rnlo: _,
4752                rnhi: _,
4753                rmlo: _,
4754                rmhi: _,
4755            } => {
4756                let mut bytes = Vec::new();
4757
4758                // PUSH {R4-R8} - save scratch registers (NO LR — inline code)
4759                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4760                bytes.extend_from_slice(&0x01F0u16.to_le_bytes());
4761
4762                // Initialize quotient (R4:R5) = 0 (computed but not returned)
4763                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4764                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4765                // Initialize remainder (R6:R7) = 0
4766                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4767                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4768                // Initialize loop counter R8 = 64
4769                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4770                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4771
4772                let loop_start = bytes.len();
4773
4774                // Shift quotient left (not needed for result, but keeps algorithm same)
4775                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4776                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4777                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4778                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4779
4780                // Shift remainder left, OR in MSB of dividend
4781                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4782                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4783                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4784                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4785                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4786                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4787
4788                // Shift dividend left
4789                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4790                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4791                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4792                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4793
4794                // Compare and conditionally subtract
4795                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4796                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4797                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4798                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4799                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4800
4801                // Subtract and set quotient bit
4802                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4803                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4804                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4805                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4806                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4807
4808                // Decrement and loop
4809                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4810                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4811
4812                let branch_offset_bytes = bytes.len() - loop_start + 4;
4813                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4814                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4815                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4816
4817                // Move REMAINDER to R0:R1 (difference from I64DivU)
4818                bytes.extend_from_slice(&0x4630u16.to_le_bytes()); // MOV R0, R6
4819                bytes.extend_from_slice(&0x4639u16.to_le_bytes()); // MOV R1, R7
4820
4821                // POP {R4-R8} - restore scratch registers (NO PC — inline code continues)
4822                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4823                bytes.extend_from_slice(&0x01F0u16.to_le_bytes());
4824
4825                Ok(bytes)
4826            }
4827
4828            // I64RemS: 64-bit signed remainder
4829            // Remainder sign follows dividend sign (not quotient rule)
4830            // Input: R0:R1 = dividend (signed), R2:R3 = divisor (signed)
4831            // Output: R0:R1 = remainder (signed, same sign as dividend)
4832            ArmOp::I64RemS {
4833                rdlo: _,
4834                rdhi: _,
4835                rnlo: _,
4836                rnhi: _,
4837                rmlo: _,
4838                rmhi: _,
4839            } => {
4840                let mut bytes = Vec::new();
4841
4842                // PUSH {R4-R11} - save scratch registers (NO LR — inline code)
4843                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4844                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4845
4846                // Save dividend sign in R9 (remainder sign = dividend sign)
4847                // MOV R9, R1 (just need the sign bit)
4848                bytes.extend_from_slice(&0x4689u16.to_le_bytes()); // MOV R9, R1
4849
4850                // If dividend negative (R1 MSB set), negate it
4851                bytes.extend_from_slice(&0x4209u16.to_le_bytes()); // TST R1, R1
4852                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4853
4854                // Negate R0:R1
4855                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4856                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4857                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4858                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4859                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4860
4861                // If divisor negative (R3 MSB set), negate it
4862                bytes.extend_from_slice(&0x421Bu16.to_le_bytes()); // TST R3, R3
4863                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4864
4865                // Negate R2:R3
4866                bytes.extend_from_slice(&0x43D2u16.to_le_bytes()); // MVNS R2, R2
4867                bytes.extend_from_slice(&0x43DBu16.to_le_bytes()); // MVNS R3, R3
4868                bytes.extend_from_slice(&0x1C52u16.to_le_bytes()); // ADDS R2, R2, #1
4869                bytes.extend_from_slice(&0xF143u16.to_le_bytes()); // ADC.W R3, R3, #0
4870                bytes.extend_from_slice(&0x0300u16.to_le_bytes());
4871
4872                // === Unsigned division algorithm ===
4873                // Initialize quotient (R4:R5) = 0
4874                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4875                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4876                // Initialize remainder (R6:R7) = 0
4877                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4878                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4879                // Initialize loop counter R8 = 64
4880                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4881                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4882
4883                let loop_start = bytes.len();
4884
4885                // Shift quotient left
4886                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4887                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4888                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4889                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4890
4891                // Shift remainder left, OR in MSB of dividend
4892                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4893                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4894                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4895                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4896                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4897                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4898
4899                // Shift dividend left
4900                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4901                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4902                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4903                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4904
4905                // Compare and conditionally subtract
4906                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4907                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4908                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4909                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4910                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4911
4912                // Subtract and set quotient bit
4913                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4914                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4915                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4916                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4917                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4918
4919                // Decrement and loop
4920                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4921                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4922
4923                let branch_offset_bytes = bytes.len() - loop_start + 4;
4924                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4925                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4926                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4927
4928                // Move remainder to R0:R1
4929                bytes.extend_from_slice(&0x4630u16.to_le_bytes()); // MOV R0, R6
4930                bytes.extend_from_slice(&0x4639u16.to_le_bytes()); // MOV R1, R7
4931
4932                // If original dividend was negative (R9 MSB set), negate remainder
4933                bytes.extend_from_slice(&0xF1B9u16.to_le_bytes()); // TST.W R9, R9
4934                bytes.extend_from_slice(&0x0F00u16.to_le_bytes());
4935                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4936
4937                // Negate result R0:R1
4938                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4939                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4940                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4941                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4942                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4943
4944                // POP {R4-R11} - restore scratch registers (NO PC — inline code continues)
4945                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4946                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4947
4948                Ok(bytes)
4949            }
4950
4951            // === F32 VFP single-precision Thumb-2 encodings ===
4952            // VFP instruction words are identical to ARM32; emit as two LE halfwords.
4953            ArmOp::F32Add { sd, sn, sm } => {
4954                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE300A00, sd, sn, sm)?))
4955            }
4956            ArmOp::F32Sub { sd, sn, sm } => {
4957                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE300A40, sd, sn, sm)?))
4958            }
4959            ArmOp::F32Mul { sd, sn, sm } => {
4960                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE200A00, sd, sn, sm)?))
4961            }
4962            ArmOp::F32Div { sd, sn, sm } => {
4963                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE800A00, sd, sn, sm)?))
4964            }
4965            ArmOp::F32Abs { sd, sm } => {
4966                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB00AC0, sd, sm)?))
4967            }
4968            ArmOp::F32Neg { sd, sm } => {
4969                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB10A40, sd, sm)?))
4970            }
4971            ArmOp::F32Sqrt { sd, sm } => {
4972                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB10AC0, sd, sm)?))
4973            }
4974
4975            // f32 pseudo-ops — multi-instruction sequences
4976            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
4977            ArmOp::F32Ceil { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b01),
4978            ArmOp::F32Floor { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b10),
4979            ArmOp::F32Trunc { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b11),
4980            ArmOp::F32Nearest { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b00),
4981            ArmOp::F32Min { sd, sn, sm } => self.encode_thumb_f32_minmax(sd, sn, sm, true),
4982            ArmOp::F32Max { sd, sn, sm } => self.encode_thumb_f32_minmax(sd, sn, sm, false),
4983            ArmOp::F32Copysign { sd, sn, sm } => self.encode_thumb_f32_copysign(sd, sn, sm),
4984
4985            // f32 comparisons — VCMP + VMRS + MOV #0 + IT + MOV #1
4986            ArmOp::F32Eq { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x0),
4987            ArmOp::F32Ne { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x1),
4988            ArmOp::F32Lt { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x4),
4989            ArmOp::F32Le { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x9),
4990            ArmOp::F32Gt { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0xC),
4991            ArmOp::F32Ge { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0xA),
4992
4993            ArmOp::F32Const { sd, value } => self.encode_thumb_f32_const(sd, *value),
4994
4995            ArmOp::F32Load { sd, addr } => {
4996                Ok(vfp_to_thumb_bytes(encode_vfp_ldst(0xED900A00, sd, addr)?))
4997            }
4998            ArmOp::F32Store { sd, addr } => {
4999                Ok(vfp_to_thumb_bytes(encode_vfp_ldst(0xED800A00, sd, addr)?))
5000            }
5001
5002            ArmOp::F32ConvertI32S { sd, rm } => self.encode_thumb_f32_convert_i32(sd, rm, true),
5003            ArmOp::F32ConvertI32U { sd, rm } => self.encode_thumb_f32_convert_i32(sd, rm, false),
5004            ArmOp::F32ConvertI64S { .. } | ArmOp::F32ConvertI64U { .. } => {
5005                Err(synth_core::Error::synthesis(
5006                    "F32 i64 conversion not supported (requires register pairs on 32-bit ARM)",
5007                ))
5008            }
5009            ArmOp::F32ReinterpretI32 { sd, rm } => {
5010                Ok(vfp_to_thumb_bytes(encode_vmov_core_sreg(true, sd, rm)?))
5011            }
5012            ArmOp::I32ReinterpretF32 { rd, sm } => {
5013                Ok(vfp_to_thumb_bytes(encode_vmov_core_sreg(false, sm, rd)?))
5014            }
5015            ArmOp::I32TruncF32S { rd, sm } => self.encode_thumb_i32_trunc_f32(rd, sm, true),
5016            ArmOp::I32TruncF32U { rd, sm } => self.encode_thumb_i32_trunc_f32(rd, sm, false),
5017
5018            // === F64 VFP double-precision Thumb-2 encodings ===
5019            // VFP instruction words are identical to ARM32; emit as two LE halfwords.
5020            ArmOp::F64Add { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
5021                0xEE300B00, dd, dn, dm,
5022            )?)),
5023            ArmOp::F64Sub { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
5024                0xEE300B40, dd, dn, dm,
5025            )?)),
5026            ArmOp::F64Mul { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
5027                0xEE200B00, dd, dn, dm,
5028            )?)),
5029            ArmOp::F64Div { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
5030                0xEE800B00, dd, dn, dm,
5031            )?)),
5032            ArmOp::F64Abs { dd, dm } => {
5033                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB00BC0, dd, dm)?))
5034            }
5035            ArmOp::F64Neg { dd, dm } => {
5036                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB10B40, dd, dm)?))
5037            }
5038            ArmOp::F64Sqrt { dd, dm } => {
5039                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB10BC0, dd, dm)?))
5040            }
5041
5042            // f64 pseudo-ops
5043            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
5044            ArmOp::F64Ceil { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b01),
5045            ArmOp::F64Floor { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b10),
5046            ArmOp::F64Trunc { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b11),
5047            ArmOp::F64Nearest { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b00),
5048            ArmOp::F64Min { dd, dn, dm } => self.encode_thumb_f64_minmax(dd, dn, dm, true),
5049            ArmOp::F64Max { dd, dn, dm } => self.encode_thumb_f64_minmax(dd, dn, dm, false),
5050            ArmOp::F64Copysign { dd, dn, dm } => self.encode_thumb_f64_copysign(dd, dn, dm),
5051
5052            // f64 comparisons
5053            ArmOp::F64Eq { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x0),
5054            ArmOp::F64Ne { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x1),
5055            ArmOp::F64Lt { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x4),
5056            ArmOp::F64Le { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x9),
5057            ArmOp::F64Gt { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0xC),
5058            ArmOp::F64Ge { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0xA),
5059
5060            ArmOp::F64Const { dd, value } => self.encode_thumb_f64_const(dd, *value),
5061
5062            ArmOp::F64Load { dd, addr } => Ok(vfp_to_thumb_bytes(encode_vfp_ldst_f64(
5063                0xED900B00, dd, addr,
5064            )?)),
5065            ArmOp::F64Store { dd, addr } => Ok(vfp_to_thumb_bytes(encode_vfp_ldst_f64(
5066                0xED800B00, dd, addr,
5067            )?)),
5068
5069            ArmOp::F64ConvertI32S { dd, rm } => self.encode_thumb_f64_convert_i32(dd, rm, true),
5070            ArmOp::F64ConvertI32U { dd, rm } => self.encode_thumb_f64_convert_i32(dd, rm, false),
5071            ArmOp::F64ConvertI64S { .. } | ArmOp::F64ConvertI64U { .. } => {
5072                Err(synth_core::Error::synthesis(
5073                    "F64 i64 conversion not supported (requires register pairs on 32-bit ARM)",
5074                ))
5075            }
5076            ArmOp::F64PromoteF32 { dd, sm } => self.encode_thumb_f64_promote_f32(dd, sm),
5077            ArmOp::F64ReinterpretI64 { dd, rmlo, rmhi } => Ok(vfp_to_thumb_bytes(
5078                encode_vmov_core_dreg(true, dd, rmlo, rmhi)?,
5079            )),
5080            ArmOp::I64ReinterpretF64 { rdlo, rdhi, dm } => Ok(vfp_to_thumb_bytes(
5081                encode_vmov_core_dreg(false, dm, rdlo, rdhi)?,
5082            )),
5083            ArmOp::I64TruncF64S { .. } | ArmOp::I64TruncF64U { .. } => {
5084                Err(synth_core::Error::synthesis(
5085                    "i64 truncation from F64 not supported (requires i64 register pairs on 32-bit ARM)",
5086                ))
5087            }
5088            ArmOp::I32TruncF64S { rd, dm } => self.encode_thumb_i32_trunc_f64(rd, dm, true),
5089            ArmOp::I32TruncF64U { rd, dm } => self.encode_thumb_i32_trunc_f64(rd, dm, false),
5090
5091            // ===== i64 operations: encode as multi-instruction Thumb-2 sequences =====
5092
5093            // I64Add: ADDS rdlo, rnlo, rmlo; ADC.W rdhi, rnhi, rmhi
5094            ArmOp::I64Add {
5095                rdlo,
5096                rdhi,
5097                rnlo,
5098                rnhi,
5099                rmlo,
5100                rmhi,
5101            } => {
5102                let mut bytes = Vec::new();
5103                // ADDS rdlo, rnlo, rmlo (16-bit)
5104                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Adds {
5105                    rd: *rdlo,
5106                    rn: *rnlo,
5107                    op2: Operand2::Reg(*rmlo),
5108                })?);
5109                // ADC.W rdhi, rnhi, rmhi (32-bit)
5110                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Adc {
5111                    rd: *rdhi,
5112                    rn: *rnhi,
5113                    op2: Operand2::Reg(*rmhi),
5114                })?);
5115                Ok(bytes)
5116            }
5117
5118            // I64Sub: SUBS rdlo, rnlo, rmlo; SBC.W rdhi, rnhi, rmhi
5119            ArmOp::I64Sub {
5120                rdlo,
5121                rdhi,
5122                rnlo,
5123                rnhi,
5124                rmlo,
5125                rmhi,
5126            } => {
5127                let mut bytes = Vec::new();
5128                // SUBS rdlo, rnlo, rmlo (16-bit)
5129                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Subs {
5130                    rd: *rdlo,
5131                    rn: *rnlo,
5132                    op2: Operand2::Reg(*rmlo),
5133                })?);
5134                // SBC.W rdhi, rnhi, rmhi (32-bit)
5135                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Sbc {
5136                    rd: *rdhi,
5137                    rn: *rnhi,
5138                    op2: Operand2::Reg(*rmhi),
5139                })?);
5140                Ok(bytes)
5141            }
5142
5143            // I64And: AND rdlo, rnlo, rmlo; AND rdhi, rnhi, rmhi
5144            ArmOp::I64And {
5145                rdlo,
5146                rdhi,
5147                rnlo,
5148                rnhi,
5149                rmlo,
5150                rmhi,
5151            } => {
5152                let mut bytes = Vec::new();
5153                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::And {
5154                    rd: *rdlo,
5155                    rn: *rnlo,
5156                    op2: Operand2::Reg(*rmlo),
5157                })?);
5158                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::And {
5159                    rd: *rdhi,
5160                    rn: *rnhi,
5161                    op2: Operand2::Reg(*rmhi),
5162                })?);
5163                Ok(bytes)
5164            }
5165
5166            // I64Or: ORR rdlo, rnlo, rmlo; ORR rdhi, rnhi, rmhi
5167            ArmOp::I64Or {
5168                rdlo,
5169                rdhi,
5170                rnlo,
5171                rnhi,
5172                rmlo,
5173                rmhi,
5174            } => {
5175                let mut bytes = Vec::new();
5176                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Orr {
5177                    rd: *rdlo,
5178                    rn: *rnlo,
5179                    op2: Operand2::Reg(*rmlo),
5180                })?);
5181                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Orr {
5182                    rd: *rdhi,
5183                    rn: *rnhi,
5184                    op2: Operand2::Reg(*rmhi),
5185                })?);
5186                Ok(bytes)
5187            }
5188
5189            // I64Xor: EOR rdlo, rnlo, rmlo; EOR rdhi, rnhi, rmhi
5190            ArmOp::I64Xor {
5191                rdlo,
5192                rdhi,
5193                rnlo,
5194                rnhi,
5195                rmlo,
5196                rmhi,
5197            } => {
5198                let mut bytes = Vec::new();
5199                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Eor {
5200                    rd: *rdlo,
5201                    rn: *rnlo,
5202                    op2: Operand2::Reg(*rmlo),
5203                })?);
5204                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Eor {
5205                    rd: *rdhi,
5206                    rn: *rnhi,
5207                    op2: Operand2::Reg(*rmhi),
5208                })?);
5209                Ok(bytes)
5210            }
5211
5212            // I64Eqz: ORR scratch, lo, hi; ITE EQ; MOV rd, #1; MOV rd, #0
5213            ArmOp::I64Eqz { rd, rnlo, rnhi } => self.encode_thumb(&ArmOp::I64SetCondZ {
5214                rd: *rd,
5215                rn_lo: *rnlo,
5216                rn_hi: *rnhi,
5217            }),
5218
5219            // I64 comparisons: delegate to I64SetCond
5220            ArmOp::I64Eq {
5221                rd,
5222                rnlo,
5223                rnhi,
5224                rmlo,
5225                rmhi,
5226            } => self.encode_thumb(&ArmOp::I64SetCond {
5227                rd: *rd,
5228                rn_lo: *rnlo,
5229                rn_hi: *rnhi,
5230                rm_lo: *rmlo,
5231                rm_hi: *rmhi,
5232                cond: synth_synthesis::Condition::EQ,
5233            }),
5234
5235            ArmOp::I64Ne {
5236                rd,
5237                rnlo,
5238                rnhi,
5239                rmlo,
5240                rmhi,
5241            } => self.encode_thumb(&ArmOp::I64SetCond {
5242                rd: *rd,
5243                rn_lo: *rnlo,
5244                rn_hi: *rnhi,
5245                rm_lo: *rmlo,
5246                rm_hi: *rmhi,
5247                cond: synth_synthesis::Condition::NE,
5248            }),
5249
5250            ArmOp::I64LtS {
5251                rd,
5252                rnlo,
5253                rnhi,
5254                rmlo,
5255                rmhi,
5256            } => self.encode_thumb(&ArmOp::I64SetCond {
5257                rd: *rd,
5258                rn_lo: *rnlo,
5259                rn_hi: *rnhi,
5260                rm_lo: *rmlo,
5261                rm_hi: *rmhi,
5262                cond: synth_synthesis::Condition::LT,
5263            }),
5264
5265            ArmOp::I64LtU {
5266                rd,
5267                rnlo,
5268                rnhi,
5269                rmlo,
5270                rmhi,
5271            } => self.encode_thumb(&ArmOp::I64SetCond {
5272                rd: *rd,
5273                rn_lo: *rnlo,
5274                rn_hi: *rnhi,
5275                rm_lo: *rmlo,
5276                rm_hi: *rmhi,
5277                cond: synth_synthesis::Condition::LO,
5278            }),
5279
5280            ArmOp::I64LeS {
5281                rd,
5282                rnlo,
5283                rnhi,
5284                rmlo,
5285                rmhi,
5286            } => self.encode_thumb(&ArmOp::I64SetCond {
5287                rd: *rd,
5288                rn_lo: *rnlo,
5289                rn_hi: *rnhi,
5290                rm_lo: *rmlo,
5291                rm_hi: *rmhi,
5292                cond: synth_synthesis::Condition::LE,
5293            }),
5294
5295            ArmOp::I64LeU {
5296                rd,
5297                rnlo,
5298                rnhi,
5299                rmlo,
5300                rmhi,
5301            } => self.encode_thumb(&ArmOp::I64SetCond {
5302                rd: *rd,
5303                rn_lo: *rnlo,
5304                rn_hi: *rnhi,
5305                rm_lo: *rmlo,
5306                rm_hi: *rmhi,
5307                cond: synth_synthesis::Condition::LS,
5308            }),
5309
5310            ArmOp::I64GtS {
5311                rd,
5312                rnlo,
5313                rnhi,
5314                rmlo,
5315                rmhi,
5316            } => self.encode_thumb(&ArmOp::I64SetCond {
5317                rd: *rd,
5318                rn_lo: *rnlo,
5319                rn_hi: *rnhi,
5320                rm_lo: *rmlo,
5321                rm_hi: *rmhi,
5322                cond: synth_synthesis::Condition::GT,
5323            }),
5324
5325            ArmOp::I64GtU {
5326                rd,
5327                rnlo,
5328                rnhi,
5329                rmlo,
5330                rmhi,
5331            } => self.encode_thumb(&ArmOp::I64SetCond {
5332                rd: *rd,
5333                rn_lo: *rnlo,
5334                rn_hi: *rnhi,
5335                rm_lo: *rmlo,
5336                rm_hi: *rmhi,
5337                cond: synth_synthesis::Condition::HI,
5338            }),
5339
5340            ArmOp::I64GeS {
5341                rd,
5342                rnlo,
5343                rnhi,
5344                rmlo,
5345                rmhi,
5346            } => self.encode_thumb(&ArmOp::I64SetCond {
5347                rd: *rd,
5348                rn_lo: *rnlo,
5349                rn_hi: *rnhi,
5350                rm_lo: *rmlo,
5351                rm_hi: *rmhi,
5352                cond: synth_synthesis::Condition::GE,
5353            }),
5354
5355            ArmOp::I64GeU {
5356                rd,
5357                rnlo,
5358                rnhi,
5359                rmlo,
5360                rmhi,
5361            } => self.encode_thumb(&ArmOp::I64SetCond {
5362                rd: *rd,
5363                rn_lo: *rnlo,
5364                rn_hi: *rnhi,
5365                rm_lo: *rmlo,
5366                rm_hi: *rmhi,
5367                cond: synth_synthesis::Condition::HS,
5368            }),
5369
5370            // I64Const: MOVW rdlo, lo16; MOVT rdlo, hi16; MOVW rdhi, lo16_hi; MOVT rdhi, hi16_hi
5371            ArmOp::I64Const { rdlo, rdhi, value } => {
5372                let lo32 = *value as u32;
5373                let hi32 = (*value >> 32) as u32;
5374                let mut bytes = Vec::new();
5375                // Load low 32 bits into rdlo
5376                bytes.extend_from_slice(
5377                    &self.encode_thumb32_movw_raw(reg_to_bits(rdlo), lo32 & 0xFFFF)?,
5378                );
5379                if lo32 > 0xFFFF {
5380                    bytes.extend_from_slice(
5381                        &self.encode_thumb32_movt_raw(reg_to_bits(rdlo), lo32 >> 16)?,
5382                    );
5383                }
5384                // Load high 32 bits into rdhi
5385                bytes.extend_from_slice(
5386                    &self.encode_thumb32_movw_raw(reg_to_bits(rdhi), hi32 & 0xFFFF)?,
5387                );
5388                if hi32 > 0xFFFF {
5389                    bytes.extend_from_slice(
5390                        &self.encode_thumb32_movt_raw(reg_to_bits(rdhi), hi32 >> 16)?,
5391                    );
5392                }
5393                Ok(bytes)
5394            }
5395
5396            // I64Ldr: LDR rdlo, [base, offset]; LDR rdhi, [base, offset+4]
5397            ArmOp::I64Ldr { rdlo, rdhi, addr } => {
5398                let mut bytes = Vec::new();
5399                // #372/#382: a memory `i64.load` carries an index register
5400                // (`reg_imm(R11, addr_reg, offset)` = R11 + addr + offset). The
5401                // immediate `encode_thumb32_ldr` below uses only base+offset and
5402                // would SILENTLY DROP `offset_reg` — the #206 defect, here for
5403                // i64. `i64_effective_base` materializes the effective base into
5404                // `ip` (and, when `offset+4 > 0xFFF`, folds the offset in too so
5405                // the function is NOT skipped — #382), returning the residual
5406                // imm12 for the two halves. Frame i64 loads (no `offset_reg`, e.g.
5407                // a spilled local at `[SP, #off]`) keep the plain `[base,#off]`
5408                // form unchanged — so existing output is byte-identical.
5409                let (base, offset) = self.i64_effective_base(&mut bytes, addr)?;
5410                bytes.extend_from_slice(&self.encode_thumb32_ldr(rdlo, &base, offset)?);
5411                bytes.extend_from_slice(&self.encode_thumb32_ldr(
5412                    rdhi,
5413                    &base,
5414                    offset.wrapping_add(4),
5415                )?);
5416                Ok(bytes)
5417            }
5418
5419            // I64Str: STR rdlo, [base, offset]; STR rdhi, [base, offset+4]
5420            ArmOp::I64Str { rdlo, rdhi, addr } => {
5421                let mut bytes = Vec::new();
5422                // #372/#382: same index-materialization + large-offset fold as
5423                // I64Ldr (see above).
5424                let (base, offset) = self.i64_effective_base(&mut bytes, addr)?;
5425                bytes.extend_from_slice(&self.encode_thumb32_str(rdlo, &base, offset)?);
5426                bytes.extend_from_slice(&self.encode_thumb32_str(
5427                    rdhi,
5428                    &base,
5429                    offset.wrapping_add(4),
5430                )?);
5431                Ok(bytes)
5432            }
5433
5434            // I64ExtendI32S: MOV rdlo, rn; ASR rdhi, rdlo, #31 (sign-extend)
5435            ArmOp::I64ExtendI32S { rdlo, rdhi, rn } => {
5436                let mut bytes = Vec::new();
5437                if rdlo != rn {
5438                    // MOV rdlo, rn (16-bit)
5439                    bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Mov {
5440                        rd: *rdlo,
5441                        op2: Operand2::Reg(*rn),
5442                    })?);
5443                }
5444                // ASR rdhi, rdlo, #31 (sign-extend: fill high word with sign bit)
5445                bytes.extend_from_slice(
5446                    &self.encode_thumb32_shift(rdhi, rdlo, 31, 0b10)?, // ASR type
5447                );
5448                Ok(bytes)
5449            }
5450
5451            // I64ExtendI32U: MOV rdlo, rn; MOV rdhi, #0
5452            ArmOp::I64ExtendI32U { rdlo, rdhi, rn } => {
5453                let mut bytes = Vec::new();
5454                if rdlo != rn {
5455                    // MOV rdlo, rn
5456                    bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Mov {
5457                        rd: *rdlo,
5458                        op2: Operand2::Reg(*rn),
5459                    })?);
5460                }
5461                // MOV rdhi, #0 (16-bit: MOVS Rd, #0)
5462                let rdhi_bits = reg_to_bits(rdhi) as u16;
5463                let instr: u16 = 0x2000 | (rdhi_bits << 8);
5464                bytes.extend_from_slice(&instr.to_le_bytes());
5465                Ok(bytes)
5466            }
5467
5468            // I32WrapI64: MOV rd, rnlo (just take low 32 bits)
5469            ArmOp::I32WrapI64 { rd, rnlo } => {
5470                if rd == rnlo {
5471                    // No-op: already in the right register
5472                    let instr: u16 = 0xBF00; // NOP
5473                    Ok(instr.to_le_bytes().to_vec())
5474                } else {
5475                    // MOV rd, rnlo
5476                    self.encode_thumb(&ArmOp::Mov {
5477                        rd: *rd,
5478                        op2: Operand2::Reg(*rnlo),
5479                    })
5480                }
5481            }
5482
5483            // ===== Helium MVE operations (Thumb-2 encoding) =====
5484            ArmOp::MveLoad { qd, addr } => Ok(vfp_to_thumb_bytes(encode_mve_vldrw(qd, addr))),
5485            ArmOp::MveStore { qd, addr } => Ok(vfp_to_thumb_bytes(encode_mve_vstrw(qd, addr))),
5486            ArmOp::MveConst { qd, bytes } => self.encode_thumb_mve_const(qd, bytes),
5487            ArmOp::MveAnd { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5488                0xEF000150, qd, qn, qm,
5489            ))),
5490            ArmOp::MveOrr { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5491                0xEF200150, qd, qn, qm,
5492            ))),
5493            ArmOp::MveEor { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5494                0xFF000150, qd, qn, qm,
5495            ))),
5496            ArmOp::MveMvn { qd, qm } => {
5497                // VMVN Qd, Qm: 0xFFB005C0 | Qd<<12 | Qm
5498                let qd_enc = qreg_to_num(qd);
5499                let qm_enc = qreg_to_num(qm);
5500                let instr: u32 = 0xFFB005C0 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5501                Ok(vfp_to_thumb_bytes(instr))
5502            }
5503            ArmOp::MveBic { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5504                0xEF100150, qd, qn, qm,
5505            ))),
5506            ArmOp::MveAddI { qd, qn, qm, size } => {
5507                let sz = mve_size_bits(size);
5508                let base: u32 = 0xEF000840 | (sz << 20);
5509                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5510            }
5511            ArmOp::MveSubI { qd, qn, qm, size } => {
5512                let sz = mve_size_bits(size);
5513                let base: u32 = 0xFF000840 | (sz << 20);
5514                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5515            }
5516            ArmOp::MveMulI { qd, qn, qm, size } => {
5517                let sz = mve_size_bits(size);
5518                let base: u32 = 0xEF000950 | (sz << 20);
5519                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5520            }
5521            ArmOp::MveNegI { qd, qm, size } => {
5522                let sz = mve_size_bits(size);
5523                // VNEG.Sx Qd, Qm
5524                let qd_enc = qreg_to_num(qd);
5525                let qm_enc = qreg_to_num(qm);
5526                let base: u32 = 0xFFB103C0 | (sz << 18);
5527                let instr = base | ((qd_enc * 2) << 12) | (qm_enc * 2);
5528                Ok(vfp_to_thumb_bytes(instr))
5529            }
5530            ArmOp::MveDup { qd, rn, size } => {
5531                let sz = mve_size_bits(size);
5532                let qd_enc = qreg_to_num(qd);
5533                let rn_bits = reg_to_bits(rn);
5534                // VDUP.sz Qd, Rn: EEA0 0B10 variant
5535                // size encoding: 00=32, 01=16, 10=8
5536                let be = match sz {
5537                    0 => 0b00u32, // 8-bit
5538                    1 => 0b01,    // 16-bit
5539                    _ => 0b00,    // 32-bit (default)
5540                };
5541                let instr: u32 = 0xEEA00B10 | ((qd_enc * 2) << 16) | (rn_bits << 12) | (be << 5);
5542                Ok(vfp_to_thumb_bytes(instr))
5543            }
5544            ArmOp::MveExtractLane { rd, qn, lane, size } => {
5545                let qn_enc = qreg_to_num(qn);
5546                let rd_bits = reg_to_bits(rd);
5547                // VMOV.sz Rd, Dn[x] — extract from Q-register lane
5548                // For 32-bit: VMOV Rd, Dn — where Dn is the appropriate D-register
5549                let d_reg = qn_enc * 2 + ((*lane as u32) >> 1);
5550                let lane_in_d = (*lane as u32) & 1;
5551                let _sz = mve_size_bits(size);
5552                // VMOV Rd, Dn[x]: EE10 0B10 for 32-bit
5553                let instr: u32 = 0xEE100B10 | (d_reg << 16) | (rd_bits << 12) | (lane_in_d << 21);
5554                Ok(vfp_to_thumb_bytes(instr))
5555            }
5556            ArmOp::MveInsertLane { qd, rn, lane, size } => {
5557                let qd_enc = qreg_to_num(qd);
5558                let rn_bits = reg_to_bits(rn);
5559                let d_reg = qd_enc * 2 + ((*lane as u32) >> 1);
5560                let lane_in_d = (*lane as u32) & 1;
5561                let _sz = mve_size_bits(size);
5562                // VMOV Dn[x], Rn: EE00 0B10 for 32-bit
5563                let instr: u32 = 0xEE000B10 | (d_reg << 16) | (rn_bits << 12) | (lane_in_d << 21);
5564                Ok(vfp_to_thumb_bytes(instr))
5565            }
5566
5567            // MVE float comparisons — emit VCMP + VPSEL sequence (simplified: just VCMP)
5568            ArmOp::MveCmpEqI { qd, qn, qm, size }
5569            | ArmOp::MveCmpNeI { qd, qn, qm, size }
5570            | ArmOp::MveCmpLtS { qd, qn, qm, size }
5571            | ArmOp::MveCmpLtU { qd, qn, qm, size }
5572            | ArmOp::MveCmpGtS { qd, qn, qm, size }
5573            | ArmOp::MveCmpGtU { qd, qn, qm, size }
5574            | ArmOp::MveCmpLeS { qd, qn, qm, size }
5575            | ArmOp::MveCmpLeU { qd, qn, qm, size }
5576            | ArmOp::MveCmpGeS { qd, qn, qm, size }
5577            | ArmOp::MveCmpGeU { qd, qn, qm, size } => {
5578                // Encode as VADD (placeholder encoding — real implementation
5579                // would use VCMP + VPSEL pair)
5580                let sz = mve_size_bits(size);
5581                let base: u32 = 0xEF000840 | (sz << 20);
5582                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5583            }
5584
5585            // f32x4 MVE arithmetic
5586            ArmOp::MveAddF32 { qd, qn, qm } => {
5587                // VADD.F32 Qd, Qn, Qm (MVE): 0xEF000D40
5588                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF000D40, qd, qn, qm)))
5589            }
5590            ArmOp::MveSubF32 { qd, qn, qm } => {
5591                // VSUB.F32 Qd, Qn, Qm (MVE): 0xEF200D40
5592                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF200D40, qd, qn, qm)))
5593            }
5594            ArmOp::MveMulF32 { qd, qn, qm } => {
5595                // VMUL.F32 Qd, Qn, Qm (MVE): 0xFF000D50
5596                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xFF000D50, qd, qn, qm)))
5597            }
5598            ArmOp::MveNegF32 { qd, qm } => {
5599                let qd_enc = qreg_to_num(qd);
5600                let qm_enc = qreg_to_num(qm);
5601                // VNEG.F32 Qd, Qm: FFB907C0
5602                let instr: u32 = 0xFFB907C0 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5603                Ok(vfp_to_thumb_bytes(instr))
5604            }
5605            ArmOp::MveAbsF32 { qd, qm } => {
5606                let qd_enc = qreg_to_num(qd);
5607                let qm_enc = qreg_to_num(qm);
5608                // VABS.F32 Qd, Qm: FFB90740
5609                let instr: u32 = 0xFFB90740 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5610                Ok(vfp_to_thumb_bytes(instr))
5611            }
5612            ArmOp::MveCmpEqF32 { qd, qn, qm }
5613            | ArmOp::MveCmpNeF32 { qd, qn, qm }
5614            | ArmOp::MveCmpLtF32 { qd, qn, qm }
5615            | ArmOp::MveCmpLeF32 { qd, qn, qm }
5616            | ArmOp::MveCmpGtF32 { qd, qn, qm }
5617            | ArmOp::MveCmpGeF32 { qd, qn, qm } => {
5618                // Placeholder: encode as VADD.F32 (real impl needs VCMP.F32 + VPSEL)
5619                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF000D40, qd, qn, qm)))
5620            }
5621            ArmOp::MveDupF32 { qd, rn } => {
5622                let qd_enc = qreg_to_num(qd);
5623                let rn_bits = reg_to_bits(rn);
5624                // VDUP.32 Qd, Rn (same encoding as integer VDUP.32)
5625                let instr: u32 = 0xEEA00B10 | ((qd_enc * 2) << 16) | (rn_bits << 12);
5626                Ok(vfp_to_thumb_bytes(instr))
5627            }
5628            ArmOp::MveExtractLaneF32 { rd, qn, lane } => {
5629                let qn_enc = qreg_to_num(qn);
5630                let rd_bits = reg_to_bits(rd);
5631                // VMOV Rd, Sn where Sn = Q*4 + lane
5632                let s_num = qn_enc * 4 + (*lane as u32);
5633                let (vn, n) = encode_sreg(s_num);
5634                let instr: u32 = 0xEE100A10 | (vn << 16) | (rd_bits << 12) | (n << 7);
5635                Ok(vfp_to_thumb_bytes(instr))
5636            }
5637            ArmOp::MveReplaceLaneF32 { qd, rn, lane } => {
5638                let qd_enc = qreg_to_num(qd);
5639                let rn_bits = reg_to_bits(rn);
5640                // VMOV Sn, Rn where Sn = Q*4 + lane
5641                let s_num = qd_enc * 4 + (*lane as u32);
5642                let (vn, n) = encode_sreg(s_num);
5643                let instr: u32 = 0xEE000A10 | (vn << 16) | (rn_bits << 12) | (n << 7);
5644                Ok(vfp_to_thumb_bytes(instr))
5645            }
5646            ArmOp::MveDivF32 { qd, qn, qm } => {
5647                // Lane-wise: extract 4 S-regs, VDIV, insert back
5648                self.encode_thumb_mve_lane_wise_f32_binop(qd, qn, qm, 0xEE800A00)
5649            }
5650            ArmOp::MveSqrtF32 { qd, qm } => {
5651                // Lane-wise: extract 4 S-regs, VSQRT, insert back
5652                self.encode_thumb_mve_lane_wise_f32_sqrt(qd, qm)
5653            }
5654
5655            // Catch-all for any remaining ops
5656            _ => {
5657                let instr: u16 = 0xBF00; // NOP
5658                Ok(instr.to_le_bytes().to_vec())
5659            }
5660        }
5661    }
5662
5663    // === Thumb-2 VFP multi-instruction helpers ===
5664
5665    /// Encode F32 comparison as Thumb-2: VCMP.F32 + VMRS + MOVS rd,#0 + IT + MOV rd,#1
5666    fn encode_thumb_f32_compare(
5667        &self,
5668        rd: &Reg,
5669        sn: &VfpReg,
5670        sm: &VfpReg,
5671        cond_code: u32,
5672    ) -> Result<Vec<u8>> {
5673        let mut bytes = Vec::new();
5674        let rd_bits = reg_to_bits(rd);
5675
5676        // VCMP.F32 Sn, Sm
5677        let sn_num = vfp_sreg_to_num(sn)?;
5678        let sm_num = vfp_sreg_to_num(sm)?;
5679        let (vd, d) = encode_sreg(sn_num);
5680        let (vm, m) = encode_sreg(sm_num);
5681        let vcmp = 0xEEB40A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5682        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5683
5684        // VMRS APSR_nzcv, FPSCR: 0xEEF1FA10
5685        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5686
5687        // MOVS Rd, #0 (16-bit): 0010 0 Rd(3) 0000 0000
5688        if rd_bits < 8 {
5689            let movs_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
5690            bytes.extend_from_slice(&movs_zero.to_le_bytes());
5691        } else {
5692            // MOV.W Rd, #0 (32-bit Thumb-2)
5693            let hw1: u16 = 0xF04F;
5694            let hw2: u16 = (rd_bits as u16) << 8;
5695            bytes.extend_from_slice(&hw1.to_le_bytes());
5696            bytes.extend_from_slice(&hw2.to_le_bytes());
5697        }
5698
5699        // IT<cond> — If-Then for conditional MOV
5700        // IT encoding: 1011 1111 cond(4) mask(4)
5701        // mask = 0x8 for single "then" (IT)
5702        let it: u16 = 0xBF00 | ((cond_code as u16) << 4) | 0x8;
5703        bytes.extend_from_slice(&it.to_le_bytes());
5704
5705        // MOV Rd, #1 (16-bit, conditional due to IT): 0010 0 Rd(3) 0000 0001
5706        if rd_bits < 8 {
5707            let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
5708            bytes.extend_from_slice(&mov_one.to_le_bytes());
5709        } else {
5710            // MOV.W Rd, #1 (32-bit)
5711            let hw1: u16 = 0xF04F;
5712            let hw2: u16 = ((rd_bits as u16) << 8) | 0x01;
5713            bytes.extend_from_slice(&hw1.to_le_bytes());
5714            bytes.extend_from_slice(&hw2.to_le_bytes());
5715        }
5716
5717        Ok(bytes)
5718    }
5719
5720    /// Encode F32 constant load as Thumb-2: MOVW + MOVT + VMOV
5721    fn encode_thumb_f32_const(&self, sd: &VfpReg, value: f32) -> Result<Vec<u8>> {
5722        let mut bytes = Vec::new();
5723        let bits = value.to_bits();
5724        let rt: u32 = 12; // R12/IP as temp
5725
5726        // MOVW R12, #lo16
5727        // Thumb-2 MOVW: 11110 i 10 0100 imm4 | 0 imm3 Rd imm8
5728        let lo16 = bits & 0xFFFF;
5729        let imm4 = (lo16 >> 12) & 0xF;
5730        let i_bit = (lo16 >> 11) & 1;
5731        let imm3 = (lo16 >> 8) & 0x7;
5732        let imm8 = lo16 & 0xFF;
5733        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
5734        let hw2: u16 = ((imm3 << 12) | (rt << 8) | imm8) as u16;
5735        bytes.extend_from_slice(&hw1.to_le_bytes());
5736        bytes.extend_from_slice(&hw2.to_le_bytes());
5737
5738        // MOVT R12, #hi16
5739        let hi16 = (bits >> 16) & 0xFFFF;
5740        let imm4 = (hi16 >> 12) & 0xF;
5741        let i_bit = (hi16 >> 11) & 1;
5742        let imm3 = (hi16 >> 8) & 0x7;
5743        let imm8 = hi16 & 0xFF;
5744        let hw1: u16 = (0xF2C0 | (i_bit << 10) | imm4) as u16;
5745        let hw2: u16 = ((imm3 << 12) | (rt << 8) | imm8) as u16;
5746        bytes.extend_from_slice(&hw1.to_le_bytes());
5747        bytes.extend_from_slice(&hw2.to_le_bytes());
5748
5749        // VMOV Sd, R12
5750        let vmov = encode_vmov_core_sreg(true, sd, &Reg::R12)?;
5751        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5752
5753        Ok(bytes)
5754    }
5755
5756    /// Encode VMOV + VCVT.F32.xS32 as Thumb-2
5757    fn encode_thumb_f32_convert_i32(&self, sd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
5758        let mut bytes = Vec::new();
5759
5760        // VMOV Sd, Rm
5761        let vmov = encode_vmov_core_sreg(true, sd, rm)?;
5762        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5763
5764        // VCVT.F32.S32/U32 Sd, Sd
5765        let sd_num = vfp_sreg_to_num(sd)?;
5766        let (vd, d) = encode_sreg(sd_num);
5767        let (vm, m) = encode_sreg(sd_num);
5768        let base = if signed { 0xEEB80A40 } else { 0xEEB80AC0 };
5769        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
5770        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
5771
5772        Ok(bytes)
5773    }
5774
5775    /// Encode F32 rounding pseudo-op as Thumb-2 via VCVT to integer and back
5776    /// Encode F32 rounding as Thumb-2.
5777    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
5778    ///
5779    /// For trunc: uses VCVTR.S32.F32 (always truncates).
5780    /// For ceil/floor/nearest: sets FPSCR rounding mode, uses VCVT.S32.F32 (non-R variant),
5781    /// then restores FPSCR.
5782    fn encode_thumb_f32_rounding(&self, sd: &VfpReg, sm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
5783        let mut bytes = Vec::new();
5784        let sm_num = vfp_sreg_to_num(sm)?;
5785        let sd_num = vfp_sreg_to_num(sd)?;
5786        let (vd_s, d_s) = encode_sreg(sd_num);
5787        let (vm_s, m_s) = encode_sreg(sm_num);
5788
5789        if mode == 0b11 {
5790            // Trunc (toward zero): VCVTR.S32.F32 — bit[7]=1, always truncates
5791            let vcvt_to_int = 0xEEBD0AC0 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
5792            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5793        } else {
5794            // ceil/floor/nearest: manipulate FPSCR rounding mode
5795            let rt: u32 = 12; // R12/IP as temp
5796
5797            // VMRS R12, FPSCR
5798            let vmrs = 0xEEF10A10 | (rt << 12);
5799            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5800
5801            // BIC.W R12, R12, #(3 << 22) — clear RMode bits [23:22]
5802            // Thumb-2 modified immediate for 3<<22 = 0x00C00000:
5803            // BIC.W encoding: 11110 i 0 0001 S Rn | 0 imm3 Rd imm8
5804            // 0x00C00000 = 0x03 shifted left by 22 => Thumb mod-imm: i=0, imm3=0b101, imm8=0x03
5805            let bic_hw1: u16 = 0xF020 | ((rt as u16) & 0xF); // BIC, Rn=R12
5806            let bic_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | 0x03;
5807            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5808            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5809
5810            // ORR.W R12, R12, #(mode << 22)
5811            if mode != 0 {
5812                let orr_hw1: u16 = 0xF040 | ((rt as u16) & 0xF); // ORR, Rn=R12
5813                let orr_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | (mode as u16);
5814                bytes.extend_from_slice(&orr_hw1.to_le_bytes());
5815                bytes.extend_from_slice(&orr_hw2.to_le_bytes());
5816            }
5817
5818            // VMSR FPSCR, R12
5819            let vmsr = 0xEEE10A10 | (rt << 12);
5820            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5821
5822            // VCVT.S32.F32 Sd, Sm — non-R variant (bit[7]=0), uses FPSCR rmode
5823            let vcvt_to_int = 0xEEBD0A40 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
5824            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5825
5826            // Restore FPSCR: clear rmode bits back to nearest (default)
5827            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5828            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5829            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5830            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5831        }
5832
5833        // VCVT.F32.S32 Sd, Sd (convert integer result back to float)
5834        let (vd2, d2) = encode_sreg(sd_num);
5835        let vcvt_to_float = 0xEEB80A40 | (d2 << 22) | (vd2 << 12) | (d_s << 5) | vd_s;
5836        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_float));
5837
5838        Ok(bytes)
5839    }
5840
5841    /// Encode F32 min/max as Thumb-2: VMOV + VCMP + VMRS + IT + VMOV
5842    fn encode_thumb_f32_minmax(
5843        &self,
5844        sd: &VfpReg,
5845        sn: &VfpReg,
5846        sm: &VfpReg,
5847        is_min: bool,
5848    ) -> Result<Vec<u8>> {
5849        let mut bytes = Vec::new();
5850        let sn_num = vfp_sreg_to_num(sn)?;
5851        let sm_num = vfp_sreg_to_num(sm)?;
5852        let sd_num = vfp_sreg_to_num(sd)?;
5853
5854        // VMOV.F32 Sd, Sn
5855        let (vd, d) = encode_sreg(sd_num);
5856        let (vn, n) = encode_sreg(sn_num);
5857        let vmov_sn = 0xEEB00A40 | (d << 22) | (vd << 12) | (n << 5) | vn;
5858        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_sn));
5859
5860        // VCMP.F32 Sn, Sm
5861        let (vm, m) = encode_sreg(sm_num);
5862        let vcmp = 0xEEB40A40 | (n << 22) | (vn << 12) | (m << 5) | vm;
5863        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5864
5865        // VMRS APSR_nzcv, FPSCR
5866        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5867
5868        // IT GT (for min) or IT MI (for max)
5869        let cond: u16 = if is_min { 0xC } else { 0x4 };
5870        let it: u16 = 0xBF00 | (cond << 4) | 0x8;
5871        bytes.extend_from_slice(&it.to_le_bytes());
5872
5873        // VMOV{cond}.F32 Sd, Sm — conditional VMOV in IT block
5874        let vmov_sm = 0xEEB00A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5875        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_sm));
5876
5877        Ok(bytes)
5878    }
5879
5880    /// Encode F32 copysign as Thumb-2
5881    fn encode_thumb_f32_copysign(&self, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
5882        let mut bytes = Vec::new();
5883
5884        // VMOV R12, Sm (get sign source bits)
5885        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5886            false,
5887            sm,
5888            &Reg::R12,
5889        )?));
5890
5891        // VMOV R0, Sn (get magnitude source bits)
5892        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5893            false,
5894            sn,
5895            &Reg::R0,
5896        )?));
5897
5898        // AND.W R12, R12, #0x80000000
5899        // Thumb-2 modified immediate: 0x80000000 = constant 0x80 with rotation
5900        // Using T1 encoding: 11110 i 0 0000 S Rn | 0 imm3 Rd imm8
5901        // 0x80000000: i=0, imm3=0b001, imm8=0x00 (rotation=4, value=0x80)
5902        // Actually encoding #0x80000000 as modified constant:
5903        // bit pattern 1 followed by 31 zeros: enc = 0b0100_00000000 = 0x0100? No.
5904        // ARM modified immediate: abcdefgh rotated. 0x80000000 = 0x80 ROR 2 = enc 0x0102
5905        // Actually: value = abcdefgh ROR (2*rot). 0x80 = 10000000, ROR 2 gives 0x20000000.
5906        // For 0x80000000: 0x02 ROR 2 = 0x80000000. So imm12 = (1<<8) | 0x02 = 0x102
5907        let hw1: u16 = 0xF000 | 12; // AND.W R12, R12, #modified_const (i=0, Rn=R12)
5908        let hw2: u16 = (0x1 << 12) | (12 << 8) | 0x02; // imm3=1, Rd=R12, imm8=0x02
5909        bytes.extend_from_slice(&hw1.to_le_bytes());
5910        bytes.extend_from_slice(&hw2.to_le_bytes());
5911
5912        // BIC.W R0, R0, #0x80000000 (R0 = register 0, fields are zero)
5913        let hw1: u16 = 0xF020; // BIC.W R0, R0, #modified_const (i=0, Rn=R0)
5914        let hw2: u16 = (0x1 << 12) | 0x02; // imm3=1, Rd=R0, imm8=0x02
5915        bytes.extend_from_slice(&hw1.to_le_bytes());
5916        bytes.extend_from_slice(&hw2.to_le_bytes());
5917
5918        // ORR.W R0, R0, R12 (R0 = register 0)
5919        let hw1: u16 = 0xEA40; // ORR.W R0, R0, R12 (Rn=R0)
5920        let hw2: u16 = 12; // Rd=R0, Rm=R12
5921        bytes.extend_from_slice(&hw1.to_le_bytes());
5922        bytes.extend_from_slice(&hw2.to_le_bytes());
5923
5924        // VMOV Sd, R0
5925        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5926            true,
5927            sd,
5928            &Reg::R0,
5929        )?));
5930
5931        Ok(bytes)
5932    }
5933
5934    /// Encode F64 comparison as Thumb-2: VCMP.F64 + VMRS + MOV #0 + IT + MOV #1
5935    fn encode_thumb_f64_compare(
5936        &self,
5937        rd: &Reg,
5938        dn: &VfpReg,
5939        dm: &VfpReg,
5940        cond_code: u32,
5941    ) -> Result<Vec<u8>> {
5942        let mut bytes = Vec::new();
5943        let rd_bits = reg_to_bits(rd);
5944
5945        // VCMP.F64 Dn, Dm
5946        let dn_num = vfp_dreg_to_num(dn)?;
5947        let dm_num = vfp_dreg_to_num(dm)?;
5948        let (vd, d) = encode_dreg(dn_num);
5949        let (vm, m) = encode_dreg(dm_num);
5950        let vcmp = 0xEEB40B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5951        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5952
5953        // VMRS APSR_nzcv, FPSCR
5954        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5955
5956        // MOVS Rd, #0
5957        if rd_bits < 8 {
5958            let movs_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
5959            bytes.extend_from_slice(&movs_zero.to_le_bytes());
5960        } else {
5961            let hw1: u16 = 0xF04F;
5962            let hw2: u16 = (rd_bits as u16) << 8;
5963            bytes.extend_from_slice(&hw1.to_le_bytes());
5964            bytes.extend_from_slice(&hw2.to_le_bytes());
5965        }
5966
5967        // IT<cond>
5968        let it: u16 = 0xBF00 | ((cond_code as u16) << 4) | 0x8;
5969        bytes.extend_from_slice(&it.to_le_bytes());
5970
5971        // MOV Rd, #1
5972        if rd_bits < 8 {
5973            let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
5974            bytes.extend_from_slice(&mov_one.to_le_bytes());
5975        } else {
5976            let hw1: u16 = 0xF04F;
5977            let hw2: u16 = ((rd_bits as u16) << 8) | 0x01;
5978            bytes.extend_from_slice(&hw1.to_le_bytes());
5979            bytes.extend_from_slice(&hw2.to_le_bytes());
5980        }
5981
5982        Ok(bytes)
5983    }
5984
5985    /// Encode F64 constant load as Thumb-2: MOVW+MOVT (lo32 into R0) + MOVW+MOVT (hi32 into R12) + VMOV Dd, R0, R12
5986    fn encode_thumb_f64_const(&self, dd: &VfpReg, value: f64) -> Result<Vec<u8>> {
5987        let mut bytes = Vec::new();
5988        let bits = value.to_bits();
5989        let lo32 = bits as u32;
5990        let hi32 = (bits >> 32) as u32;
5991
5992        // MOVW R0, #lo16(lo32)
5993        let lo16 = lo32 & 0xFFFF;
5994        bytes.extend_from_slice(&self.encode_thumb32_movw_raw(0, lo16)?);
5995
5996        // MOVT R0, #hi16(lo32)
5997        let hi16 = (lo32 >> 16) & 0xFFFF;
5998        bytes.extend_from_slice(&self.encode_thumb32_movt_raw(0, hi16)?);
5999
6000        // MOVW R12, #lo16(hi32)
6001        let lo16 = hi32 & 0xFFFF;
6002        bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, lo16)?);
6003
6004        // MOVT R12, #hi16(hi32)
6005        let hi16 = (hi32 >> 16) & 0xFFFF;
6006        bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, hi16)?);
6007
6008        // VMOV Dd, R0, R12
6009        let vmov = encode_vmov_core_dreg(true, dd, &Reg::R0, &Reg::R12)?;
6010        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
6011
6012        Ok(bytes)
6013    }
6014
6015    /// Encode VMOV Sd, Rm + VCVT.F64.S32/U32 Dd, Sd as Thumb-2
6016    fn encode_thumb_f64_convert_i32(&self, dd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
6017        let mut bytes = Vec::new();
6018
6019        // VMOV S0, Rm
6020        let vmov = encode_vmov_core_sreg(true, &VfpReg::S0, rm)?;
6021        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
6022
6023        // VCVT.F64.S32 Dd, S0 or VCVT.F64.U32 Dd, S0
6024        let dd_num = vfp_dreg_to_num(dd)?;
6025        let (vd, d) = encode_dreg(dd_num);
6026        let base = if signed { 0xEEB80B40 } else { 0xEEB80BC0 };
6027        let vcvt = base | (d << 22) | (vd << 12);
6028        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
6029
6030        Ok(bytes)
6031    }
6032
6033    /// Encode VCVT.F64.F32 Dd, Sm as Thumb-2
6034    fn encode_thumb_f64_promote_f32(&self, dd: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
6035        let dd_num = vfp_dreg_to_num(dd)?;
6036        let sm_num = vfp_sreg_to_num(sm)?;
6037        let (vd, d) = encode_dreg(dd_num);
6038        let (vm, m) = encode_sreg(sm_num);
6039
6040        let vcvt = 0xEEB70AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
6041        Ok(vfp_to_thumb_bytes(vcvt))
6042    }
6043
6044    /// Encode VCVT.S32/U32.F64 S0, Dm + VMOV Rd, S0 as Thumb-2
6045    fn encode_thumb_i32_trunc_f64(&self, rd: &Reg, dm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
6046        let mut bytes = Vec::new();
6047        let dm_num = vfp_dreg_to_num(dm)?;
6048        let (vm, m) = encode_dreg(dm_num);
6049
6050        // VCVT.S32.F64 S0, Dm or VCVT.U32.F64 S0, Dm
6051        let base = if signed { 0xEEBD0BC0 } else { 0xEEBC0BC0 };
6052        let vcvt = base | (m << 5) | vm;
6053        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
6054
6055        // VMOV Rd, S0
6056        let vmov = encode_vmov_core_sreg(false, &VfpReg::S0, rd)?;
6057        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
6058
6059        Ok(bytes)
6060    }
6061
6062    /// Encode F64 rounding pseudo-op as Thumb-2 via VCVT to integer and back
6063    /// Encode F64 rounding as Thumb-2.
6064    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
6065    fn encode_thumb_f64_rounding(&self, dd: &VfpReg, dm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
6066        let mut bytes = Vec::new();
6067        let dm_num = vfp_dreg_to_num(dm)?;
6068        let dd_num = vfp_dreg_to_num(dd)?;
6069        let (vm, m) = encode_dreg(dm_num);
6070        let (vd, d) = encode_dreg(dd_num);
6071
6072        if mode == 0b11 {
6073            // Trunc: VCVTR.S32.F64 — bit[7]=1, always truncates
6074            let vcvt_to_int = 0xEEBD0BC0 | (m << 5) | vm;
6075            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
6076        } else {
6077            let rt: u32 = 12;
6078
6079            // VMRS R12, FPSCR
6080            let vmrs = 0xEEF10A10 | (rt << 12);
6081            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
6082
6083            // BIC.W R12, R12, #(3 << 22)
6084            let bic_hw1: u16 = 0xF020 | ((rt as u16) & 0xF);
6085            let bic_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | 0x03;
6086            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
6087            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
6088
6089            // ORR.W R12, R12, #(mode << 22)
6090            if mode != 0 {
6091                let orr_hw1: u16 = 0xF040 | ((rt as u16) & 0xF);
6092                let orr_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | (mode as u16);
6093                bytes.extend_from_slice(&orr_hw1.to_le_bytes());
6094                bytes.extend_from_slice(&orr_hw2.to_le_bytes());
6095            }
6096
6097            // VMSR FPSCR, R12
6098            let vmsr = 0xEEE10A10 | (rt << 12);
6099            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
6100
6101            // VCVT.S32.F64 S0, Dm — non-R variant (bit[7]=0)
6102            let vcvt_to_int = 0xEEBD0B40 | (m << 5) | vm;
6103            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
6104
6105            // Restore FPSCR
6106            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
6107            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
6108            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
6109            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
6110        }
6111
6112        // VCVT.F64.S32 Dd, S0
6113        let vcvt_to_float = 0xEEB80B40 | (d << 22) | (vd << 12);
6114        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_float));
6115
6116        Ok(bytes)
6117    }
6118
6119    /// Encode F64 min/max as Thumb-2
6120    fn encode_thumb_f64_minmax(
6121        &self,
6122        dd: &VfpReg,
6123        dn: &VfpReg,
6124        dm: &VfpReg,
6125        is_min: bool,
6126    ) -> Result<Vec<u8>> {
6127        let mut bytes = Vec::new();
6128        let dn_num = vfp_dreg_to_num(dn)?;
6129        let dm_num = vfp_dreg_to_num(dm)?;
6130        let dd_num = vfp_dreg_to_num(dd)?;
6131
6132        // VMOV.F64 Dd, Dn
6133        let (vd, d) = encode_dreg(dd_num);
6134        let (vn, n) = encode_dreg(dn_num);
6135        let vmov_dn = 0xEEB00B40 | (d << 22) | (vd << 12) | (n << 5) | vn;
6136        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_dn));
6137
6138        // VCMP.F64 Dn, Dm
6139        let (vm, m) = encode_dreg(dm_num);
6140        let vcmp = 0xEEB40B40 | (n << 22) | (vn << 12) | (m << 5) | vm;
6141        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
6142
6143        // VMRS APSR_nzcv, FPSCR
6144        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
6145
6146        // IT GT (for min) or IT MI (for max)
6147        let cond: u16 = if is_min { 0xC } else { 0x4 };
6148        let it: u16 = 0xBF00 | (cond << 4) | 0x8;
6149        bytes.extend_from_slice(&it.to_le_bytes());
6150
6151        // VMOV{cond}.F64 Dd, Dm
6152        let vmov_dm = 0xEEB00B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
6153        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_dm));
6154
6155        Ok(bytes)
6156    }
6157
6158    /// Encode F64 copysign as Thumb-2
6159    fn encode_thumb_f64_copysign(&self, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<Vec<u8>> {
6160        let mut bytes = Vec::new();
6161
6162        // VMOV R0, R12, Dm (get sign source)
6163        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
6164            false,
6165            dm,
6166            &Reg::R0,
6167            &Reg::R12,
6168        )?));
6169
6170        // VMOV R1, R2, Dn (get magnitude source)
6171        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
6172            false,
6173            dn,
6174            &Reg::R1,
6175            &Reg::R2,
6176        )?));
6177
6178        // AND.W R12, R12, #0x80000000 (i=0, Rn=R12)
6179        let hw1: u16 = 0xF000 | 12;
6180        let hw2: u16 = (0x1 << 12) | (12 << 8) | 0x02;
6181        bytes.extend_from_slice(&hw1.to_le_bytes());
6182        bytes.extend_from_slice(&hw2.to_le_bytes());
6183
6184        // BIC.W R2, R2, #0x80000000 (i=0, Rn=R2)
6185        let hw1: u16 = 0xF020 | 2;
6186        let hw2: u16 = (0x1 << 12) | (2 << 8) | 0x02;
6187        bytes.extend_from_slice(&hw1.to_le_bytes());
6188        bytes.extend_from_slice(&hw2.to_le_bytes());
6189
6190        // ORR.W R2, R2, R12
6191        let hw1: u16 = 0xEA40 | 2;
6192        let hw2: u16 = (2 << 8) | 12;
6193        bytes.extend_from_slice(&hw1.to_le_bytes());
6194        bytes.extend_from_slice(&hw2.to_le_bytes());
6195
6196        // VMOV Dd, R1, R2
6197        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
6198            true,
6199            dd,
6200            &Reg::R1,
6201            &Reg::R2,
6202        )?));
6203
6204        Ok(bytes)
6205    }
6206
6207    /// Encode VCVT.S32/U32.F32 + VMOV as Thumb-2
6208    fn encode_thumb_i32_trunc_f32(&self, rd: &Reg, sm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
6209        let mut bytes = Vec::new();
6210
6211        let sm_num = vfp_sreg_to_num(sm)?;
6212        let (vd, d) = encode_sreg(sm_num);
6213        let (vm, m) = encode_sreg(sm_num);
6214        let base = if signed { 0xEEBD0AC0 } else { 0xEEBC0AC0 };
6215        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
6216        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
6217
6218        // VMOV Rd, Sm
6219        let vmov = encode_vmov_core_sreg(false, sm, rd)?;
6220        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
6221
6222        Ok(bytes)
6223    }
6224
6225    // === Thumb-2 32-bit encoding helpers ===
6226
6227    /// Encode Thumb-2 32-bit ADD with immediate
6228    fn encode_thumb32_add(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6229        let rd_bits = reg_to_bits(rd);
6230        let rn_bits = reg_to_bits(rn);
6231
6232        // The `i:imm3:imm8` field is split the same way for both forms.
6233        let i_bit = (imm >> 11) & 1;
6234        let imm3 = (imm >> 8) & 0x7;
6235        let imm8 = imm & 0xFF;
6236
6237        let hw1_base = if imm <= 0xFF {
6238            // ADD.W (T3): the field is a ThumbExpandImm modified immediate. For
6239            // imm <= 0xFF (i:imm3 = 0000) it is the zero-extended byte, which is
6240            // correct — keep this form so existing encodings stay bit-identical.
6241            0xF100
6242        } else if imm <= 0xFFF {
6243            // ADDW (T4): a PLAIN 12-bit immediate (0..4095) — no ThumbExpandImm.
6244            // This is what makes `add sp, sp, #frame` correct for frame sizes
6245            // >= 256, which ADD.W (T3) would silently mis-encode (e.g. #256 -> #0).
6246            0xF200
6247        } else {
6248            return Err(synth_core::Error::synthesis(
6249                "ADD immediate > 0xFFF (4095) requires a multi-instruction sequence (not supported)",
6250            ));
6251        };
6252
6253        let hw1: u16 = (hw1_base | (i_bit << 10) | rn_bits) as u16;
6254        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6255
6256        let mut bytes = hw1.to_le_bytes().to_vec();
6257        bytes.extend_from_slice(&hw2.to_le_bytes());
6258        Ok(bytes)
6259    }
6260
6261    /// Encode Thumb-2 32-bit SUB with immediate
6262    fn encode_thumb32_sub(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6263        let rd_bits = reg_to_bits(rd);
6264        let rn_bits = reg_to_bits(rn);
6265
6266        let i_bit = (imm >> 11) & 1;
6267        let imm3 = (imm >> 8) & 0x7;
6268        let imm8 = imm & 0xFF;
6269
6270        let hw1_base = if imm <= 0xFF {
6271            // SUB.W (T3) modified immediate — correct for the zero-extended byte
6272            // (imm <= 0xFF). Kept bit-identical for existing encodings.
6273            0xF1A0
6274        } else if imm <= 0xFFF {
6275            // SUBW (T4): plain 12-bit immediate (0..4095). Makes
6276            // `sub sp, sp, #frame` correct for frame sizes >= 256.
6277            0xF2A0
6278        } else {
6279            return Err(synth_core::Error::synthesis(
6280                "SUB immediate > 0xFFF (4095) requires a multi-instruction sequence (not supported)",
6281            ));
6282        };
6283
6284        let hw1: u16 = (hw1_base | (i_bit << 10) | rn_bits) as u16;
6285        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6286
6287        let mut bytes = hw1.to_le_bytes().to_vec();
6288        bytes.extend_from_slice(&hw2.to_le_bytes());
6289        Ok(bytes)
6290    }
6291
6292    /// Encode Thumb-2 32-bit ADDS with immediate (sets flags)
6293    fn encode_thumb32_adds(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6294        let rd_bits = reg_to_bits(rd);
6295        let rn_bits = reg_to_bits(rn);
6296
6297        // ADDS.W (flag-setting) has only the modified-immediate form — error on
6298        // an un-encodable value rather than silently add the wrong constant.
6299        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6300            synth_core::Error::synthesis(
6301                "ADDS immediate is not a valid ThumbExpandImm — materialize into a register",
6302            )
6303        })?;
6304        let i_bit = (field >> 11) & 1;
6305        let imm3 = (field >> 8) & 0x7;
6306        let imm8 = field & 0xFF;
6307
6308        // ADDS.W Rd, Rn, #imm (with S=1)
6309        // First halfword: 1111 0 i 0 1000 1 Rn = F110 | i<<10 | Rn
6310        let hw1: u16 = (0xF110 | (i_bit << 10) | rn_bits) as u16;
6311        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6312
6313        let mut bytes = hw1.to_le_bytes().to_vec();
6314        bytes.extend_from_slice(&hw2.to_le_bytes());
6315        Ok(bytes)
6316    }
6317
6318    /// Encode Thumb-2 32-bit SUBS with immediate (sets flags)
6319    fn encode_thumb32_subs(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6320        let rd_bits = reg_to_bits(rd);
6321        let rn_bits = reg_to_bits(rn);
6322
6323        // SUBS.W (flag-setting) has only the modified-immediate form — error on
6324        // an un-encodable value rather than silently subtract the wrong constant.
6325        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6326            synth_core::Error::synthesis(
6327                "SUBS immediate is not a valid ThumbExpandImm — materialize into a register",
6328            )
6329        })?;
6330        let i_bit = (field >> 11) & 1;
6331        let imm3 = (field >> 8) & 0x7;
6332        let imm8 = field & 0xFF;
6333
6334        // SUBS.W Rd, Rn, #imm (with S=1)
6335        // First halfword: 1111 0 i 0 1101 1 Rn = F1B0 | i<<10 | Rn
6336        let hw1: u16 = (0xF1B0 | (i_bit << 10) | rn_bits) as u16;
6337        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6338
6339        let mut bytes = hw1.to_le_bytes().to_vec();
6340        bytes.extend_from_slice(&hw2.to_le_bytes());
6341        Ok(bytes)
6342    }
6343
6344    /// Encode Thumb-2 32-bit MOVW (16-bit immediate)
6345    ///
6346    /// # Contract (Verus-style)
6347    /// ```text
6348    /// requires rd <= R14
6349    /// ensures result.len() == 4
6350    /// ensures (imm & 0xFFFF) can be reconstructed from the encoding
6351    /// ```
6352    fn encode_thumb32_movw(&self, rd: &Reg, imm: u32) -> Result<Vec<u8>> {
6353        let rd_bits = reg_to_bits(rd);
6354        reg_bits_checked(rd_bits)?;
6355        let imm16 = imm & 0xFFFF;
6356
6357        // MOVW Rd, #imm16
6358        // 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
6359        let imm4 = (imm16 >> 12) & 0xF;
6360        let i_bit = (imm16 >> 11) & 1;
6361        let imm3 = (imm16 >> 8) & 0x7;
6362        let imm8 = imm16 & 0xFF;
6363
6364        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
6365        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6366
6367        let mut bytes = hw1.to_le_bytes().to_vec();
6368        bytes.extend_from_slice(&hw2.to_le_bytes());
6369        encoding_contracts::verify_thumb32(&bytes);
6370        Ok(bytes)
6371    }
6372
6373    /// Encode Thumb-2 32-bit shift with immediate
6374    ///
6375    /// # Contract (Verus-style)
6376    /// ```text
6377    /// requires rd <= R14, rm <= R14
6378    /// ensures result.len() == 4
6379    /// ```
6380    fn encode_thumb32_shift(
6381        &self,
6382        rd: &Reg,
6383        rm: &Reg,
6384        shift: u32,
6385        shift_type: u8,
6386    ) -> Result<Vec<u8>> {
6387        let rd_bits = reg_to_bits(rd);
6388        let rm_bits = reg_to_bits(rm);
6389        reg_bits_checked(rd_bits)?;
6390        reg_bits_checked(rm_bits)?;
6391        let imm5 = shift & 0x1F;
6392        let imm2 = imm5 & 0x3;
6393        let imm3 = (imm5 >> 2) & 0x7;
6394
6395        // MOV.W Rd, Rm, <shift> #imm
6396        // EA4F 0 imm3 Rd imm2 type Rm
6397        let hw1: u16 = 0xEA4F;
6398        let hw2: u16 =
6399            ((imm3 << 12) | (rd_bits << 8) | (imm2 << 6) | ((shift_type as u32) << 4) | rm_bits)
6400                as u16;
6401
6402        let mut bytes = hw1.to_le_bytes().to_vec();
6403        bytes.extend_from_slice(&hw2.to_le_bytes());
6404        Ok(bytes)
6405    }
6406
6407    /// Encode Thumb-2 32-bit shift by register
6408    /// Encoding: 11111010 0xx0 Rn | 1111 Rd 0000 Rm
6409    /// shift_type: 00=LSL, 01=LSR, 10=ASR, 11=ROR
6410    fn encode_thumb32_shift_reg(
6411        &self,
6412        rd: &Reg,
6413        rn: &Reg,
6414        rm: &Reg,
6415        shift_type: u8,
6416    ) -> Result<Vec<u8>> {
6417        let rd_bits = reg_to_bits(rd);
6418        let rn_bits = reg_to_bits(rn);
6419        let rm_bits = reg_to_bits(rm);
6420
6421        // hw1: 1111 1010 0xx0 Rn
6422        let hw1: u16 = (0xFA00 | ((shift_type as u32) << 5) | rn_bits) as u16;
6423        // hw2: 1111 Rd 0000 Rm
6424        let hw2: u16 = (0xF000 | (rd_bits << 8) | rm_bits) as u16;
6425
6426        let mut bytes = hw1.to_le_bytes().to_vec();
6427        bytes.extend_from_slice(&hw2.to_le_bytes());
6428        Ok(bytes)
6429    }
6430
6431    /// Encode Thumb-2 32-bit CMP with immediate
6432    fn encode_thumb32_cmp_imm(&self, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6433        let rn_bits = reg_to_bits(rn);
6434
6435        // CMP.W has only the modified-immediate form (no plain-imm12 like ADDW),
6436        // so an un-encodable immediate MUST be materialized into a register by
6437        // the selector. Error rather than silently compare the wrong constant.
6438        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6439            synth_core::Error::synthesis(
6440                "CMP immediate is not a valid ThumbExpandImm — materialize into a register",
6441            )
6442        })?;
6443        let i_bit = (field >> 11) & 1;
6444        let imm3 = (field >> 8) & 0x7;
6445        let imm8 = field & 0xFF;
6446
6447        // CMP.W Rn, #imm
6448        let hw1: u16 = (0xF1B0 | (i_bit << 10) | rn_bits) as u16;
6449        let hw2: u16 = ((imm3 << 12) | 0x0F00 | imm8) as u16;
6450
6451        let mut bytes = hw1.to_le_bytes().to_vec();
6452        bytes.extend_from_slice(&hw2.to_le_bytes());
6453        Ok(bytes)
6454    }
6455
6456    /// #372/#382: resolve the base register AND residual immediate offset for an
6457    /// `I64Ldr`/`I64Str` whose address may carry an index register. Returns
6458    /// `(base, low_offset)`; the caller accesses the halves at `[base,
6459    /// #low_offset]` and `[base, #low_offset + 4]`.
6460    ///
6461    /// - Frame access (no `offset_reg`, e.g. a spilled local at `[SP, #off]`):
6462    ///   returns `(addr.base, off)` and emits NOTHING — byte-identical.
6463    /// - Memory access (`reg_imm(R11, addr, offset)` = `R11 + addr + offset`)
6464    ///   with `offset + 4 <= 0xFFF`: emits `ADD.W ip, base, index` and returns
6465    ///   `(ip, offset)`, folding `offset`/`offset+4` into the halves' imm12.
6466    ///   Byte-identical to the pre-#382 (#372) behavior.
6467    /// - Memory access with `offset + 4 > 0xFFF`: the imm12 form cannot hold the
6468    ///   high half's offset, so `encode_thumb32_ldr`'s `check_ldst_imm12` (#259)
6469    ///   rightly refused it and the WHOLE function was skipped (#382). Instead
6470    ///   MATERIALIZE the offset into the base: `ADD ip, index, #offset` (against
6471    ///   the read-only INDEX register, so `encode_thumb32_add_imm` never trips its
6472    ///   `rd==rn==R12` alias trap), then `ADD.W ip, ip, base` (+ R11), and return
6473    ///   `(ip, 0)` so the halves use `[ip, #0]` / `[ip, #4]`.
6474    ///
6475    /// The effective address is fully materialized into `ip` BEFORE the halves
6476    /// are accessed, so an `rdlo` aliasing the index register is safe.
6477    fn i64_effective_base(&self, bytes: &mut Vec<u8>, addr: &MemAddr) -> Result<(Reg, u32)> {
6478        let offset = if addr.offset < 0 {
6479            0u32
6480        } else {
6481            addr.offset as u32
6482        };
6483        match addr.offset_reg {
6484            Some(idx) => {
6485                let ip = Reg::R12;
6486                if offset.wrapping_add(4) > 0xFFF {
6487                    // Large static offset (#382): fold it (and R11) into ip so the
6488                    // imm12 halves stay in range instead of skipping the function.
6489                    // ADD ip, index, #offset  (index != ip → no add_imm alias trap)
6490                    bytes.extend_from_slice(&self.encode_thumb32_add_imm(&ip, &idx, offset)?);
6491                    // ADD.W ip, ip, base  (+ R11)
6492                    bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
6493                        reg_to_bits(&ip),
6494                        reg_to_bits(&ip),
6495                        reg_to_bits(&addr.base),
6496                    )?);
6497                    Ok((ip, 0))
6498                } else {
6499                    // ADD.W ip, addr.base, idx  (Thumb-2, byte-verified vs as)
6500                    let hw1: u16 = 0xEB00 | reg_to_bits(&addr.base) as u16;
6501                    let hw2: u16 = 0x0C00 | reg_to_bits(&idx) as u16;
6502                    bytes.extend_from_slice(&hw1.to_le_bytes());
6503                    bytes.extend_from_slice(&hw2.to_le_bytes());
6504                    Ok((ip, offset))
6505                }
6506            }
6507            None => Ok((addr.base, offset)),
6508        }
6509    }
6510
6511    /// Encode Thumb-2 32-bit LDR
6512    fn encode_thumb32_ldr(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6513        let rd_bits = reg_to_bits(rd);
6514        let base_bits = reg_to_bits(base);
6515
6516        // LDR.W Rd, [Rn, #imm12]
6517        check_ldst_imm12(offset)?;
6518        let hw1: u16 = (0xF8D0 | base_bits) as u16;
6519        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6520
6521        let mut bytes = hw1.to_le_bytes().to_vec();
6522        bytes.extend_from_slice(&hw2.to_le_bytes());
6523        Ok(bytes)
6524    }
6525
6526    /// Encode Thumb-2 32-bit STR
6527    fn encode_thumb32_str(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6528        let rd_bits = reg_to_bits(rd);
6529        let base_bits = reg_to_bits(base);
6530
6531        // STR.W Rd, [Rn, #imm12]
6532        check_ldst_imm12(offset)?;
6533        let hw1: u16 = (0xF8C0 | base_bits) as u16;
6534        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6535
6536        let mut bytes = hw1.to_le_bytes().to_vec();
6537        bytes.extend_from_slice(&hw2.to_le_bytes());
6538        Ok(bytes)
6539    }
6540
6541    /// Encode Thumb-2 32-bit LDR with register offset: LDR.W Rd, [Rn, Rm]
6542    fn encode_thumb32_ldr_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6543        let rd_bits = reg_to_bits(rd);
6544        let base_bits = reg_to_bits(base);
6545        let rm_bits = reg_to_bits(offset_reg);
6546
6547        // LDR.W Rd, [Rn, Rm, LSL #0]
6548        // Encoding: 1111 1000 0101 Rn | Rt 0000 00 imm2 Rm
6549        // imm2 = 00 for no shift (LSL #0)
6550        let hw1: u16 = (0xF850 | base_bits) as u16;
6551        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6552
6553        let mut bytes = hw1.to_le_bytes().to_vec();
6554        bytes.extend_from_slice(&hw2.to_le_bytes());
6555        Ok(bytes)
6556    }
6557
6558    /// Encode Thumb-2 32-bit STR with register offset: STR.W Rd, [Rn, Rm]
6559    fn encode_thumb32_str_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6560        let rd_bits = reg_to_bits(rd);
6561        let base_bits = reg_to_bits(base);
6562        let rm_bits = reg_to_bits(offset_reg);
6563
6564        // STR.W Rd, [Rn, Rm, LSL #0]
6565        // Encoding: 1111 1000 0100 Rn | Rt 0000 00 imm2 Rm
6566        // imm2 = 00 for no shift (LSL #0)
6567        let hw1: u16 = (0xF840 | base_bits) as u16;
6568        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6569
6570        let mut bytes = hw1.to_le_bytes().to_vec();
6571        bytes.extend_from_slice(&hw2.to_le_bytes());
6572        Ok(bytes)
6573    }
6574
6575    // === Sub-word load/store Thumb-2 encoding helpers ===
6576
6577    /// Encode Thumb-2 32-bit LDRB with immediate: LDRB.W Rd, [Rn, #imm12]
6578    fn encode_thumb32_ldrb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6579        let rd_bits = reg_to_bits(rd);
6580        let base_bits = reg_to_bits(base);
6581        // LDRB.W Rd, [Rn, #imm12]: 1111 1000 1001 Rn | Rt imm12
6582        check_ldst_imm12(offset)?;
6583        let hw1: u16 = (0xF890 | base_bits) as u16;
6584        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6585        let mut bytes = hw1.to_le_bytes().to_vec();
6586        bytes.extend_from_slice(&hw2.to_le_bytes());
6587        Ok(bytes)
6588    }
6589
6590    /// Encode Thumb-2 32-bit LDRB with register: LDRB.W Rd, [Rn, Rm]
6591    fn encode_thumb32_ldrb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6592        let rd_bits = reg_to_bits(rd);
6593        let base_bits = reg_to_bits(base);
6594        let rm_bits = reg_to_bits(offset_reg);
6595        // LDRB.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0001 Rn | Rt 0000 00 imm2 Rm
6596        let hw1: u16 = (0xF810 | base_bits) as u16;
6597        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6598        let mut bytes = hw1.to_le_bytes().to_vec();
6599        bytes.extend_from_slice(&hw2.to_le_bytes());
6600        Ok(bytes)
6601    }
6602
6603    /// Encode Thumb-2 32-bit LDRSB with immediate: LDRSB.W Rd, [Rn, #imm12]
6604    fn encode_thumb32_ldrsb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6605        let rd_bits = reg_to_bits(rd);
6606        let base_bits = reg_to_bits(base);
6607        // LDRSB.W Rd, [Rn, #imm12]: 1111 1001 1001 Rn | Rt imm12
6608        check_ldst_imm12(offset)?;
6609        let hw1: u16 = (0xF990 | base_bits) as u16;
6610        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6611        let mut bytes = hw1.to_le_bytes().to_vec();
6612        bytes.extend_from_slice(&hw2.to_le_bytes());
6613        Ok(bytes)
6614    }
6615
6616    /// Encode Thumb-2 32-bit LDRSB with register: LDRSB.W Rd, [Rn, Rm]
6617    fn encode_thumb32_ldrsb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6618        let rd_bits = reg_to_bits(rd);
6619        let base_bits = reg_to_bits(base);
6620        let rm_bits = reg_to_bits(offset_reg);
6621        // LDRSB.W Rd, [Rn, Rm, LSL #0]: 1111 1001 0001 Rn | Rt 0000 00 imm2 Rm
6622        let hw1: u16 = (0xF910 | base_bits) as u16;
6623        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6624        let mut bytes = hw1.to_le_bytes().to_vec();
6625        bytes.extend_from_slice(&hw2.to_le_bytes());
6626        Ok(bytes)
6627    }
6628
6629    /// Encode Thumb-2 32-bit LDRH with immediate: LDRH.W Rd, [Rn, #imm12]
6630    fn encode_thumb32_ldrh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6631        let rd_bits = reg_to_bits(rd);
6632        let base_bits = reg_to_bits(base);
6633        // LDRH.W Rd, [Rn, #imm12]: 1111 1000 1011 Rn | Rt imm12
6634        check_ldst_imm12(offset)?;
6635        let hw1: u16 = (0xF8B0 | base_bits) as u16;
6636        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6637        let mut bytes = hw1.to_le_bytes().to_vec();
6638        bytes.extend_from_slice(&hw2.to_le_bytes());
6639        Ok(bytes)
6640    }
6641
6642    /// Encode Thumb-2 32-bit LDRH with register: LDRH.W Rd, [Rn, Rm]
6643    fn encode_thumb32_ldrh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6644        let rd_bits = reg_to_bits(rd);
6645        let base_bits = reg_to_bits(base);
6646        let rm_bits = reg_to_bits(offset_reg);
6647        // LDRH.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0011 Rn | Rt 0000 00 imm2 Rm
6648        let hw1: u16 = (0xF830 | base_bits) as u16;
6649        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6650        let mut bytes = hw1.to_le_bytes().to_vec();
6651        bytes.extend_from_slice(&hw2.to_le_bytes());
6652        Ok(bytes)
6653    }
6654
6655    /// Encode Thumb-2 32-bit LDRSH with immediate: LDRSH.W Rd, [Rn, #imm12]
6656    fn encode_thumb32_ldrsh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6657        let rd_bits = reg_to_bits(rd);
6658        let base_bits = reg_to_bits(base);
6659        // LDRSH.W Rd, [Rn, #imm12]: 1111 1001 1011 Rn | Rt imm12
6660        check_ldst_imm12(offset)?;
6661        let hw1: u16 = (0xF9B0 | base_bits) as u16;
6662        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6663        let mut bytes = hw1.to_le_bytes().to_vec();
6664        bytes.extend_from_slice(&hw2.to_le_bytes());
6665        Ok(bytes)
6666    }
6667
6668    /// Encode Thumb-2 32-bit LDRSH with register: LDRSH.W Rd, [Rn, Rm]
6669    fn encode_thumb32_ldrsh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6670        let rd_bits = reg_to_bits(rd);
6671        let base_bits = reg_to_bits(base);
6672        let rm_bits = reg_to_bits(offset_reg);
6673        // LDRSH.W Rd, [Rn, Rm, LSL #0]: 1111 1001 0011 Rn | Rt 0000 00 imm2 Rm
6674        let hw1: u16 = (0xF930 | base_bits) as u16;
6675        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6676        let mut bytes = hw1.to_le_bytes().to_vec();
6677        bytes.extend_from_slice(&hw2.to_le_bytes());
6678        Ok(bytes)
6679    }
6680
6681    /// Encode Thumb-2 32-bit STRB with immediate: STRB.W Rd, [Rn, #imm12]
6682    fn encode_thumb32_strb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6683        let rd_bits = reg_to_bits(rd);
6684        let base_bits = reg_to_bits(base);
6685        // STRB.W Rd, [Rn, #imm12]: 1111 1000 1000 Rn | Rt imm12
6686        check_ldst_imm12(offset)?;
6687        let hw1: u16 = (0xF880 | base_bits) as u16;
6688        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6689        let mut bytes = hw1.to_le_bytes().to_vec();
6690        bytes.extend_from_slice(&hw2.to_le_bytes());
6691        Ok(bytes)
6692    }
6693
6694    /// Encode Thumb-2 32-bit STRB with register: STRB.W Rd, [Rn, Rm]
6695    fn encode_thumb32_strb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6696        let rd_bits = reg_to_bits(rd);
6697        let base_bits = reg_to_bits(base);
6698        let rm_bits = reg_to_bits(offset_reg);
6699        // STRB.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0000 Rn | Rt 0000 00 imm2 Rm
6700        let hw1: u16 = (0xF800 | base_bits) as u16;
6701        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6702        let mut bytes = hw1.to_le_bytes().to_vec();
6703        bytes.extend_from_slice(&hw2.to_le_bytes());
6704        Ok(bytes)
6705    }
6706
6707    /// Encode Thumb-2 32-bit STRH with immediate: STRH.W Rd, [Rn, #imm12]
6708    fn encode_thumb32_strh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6709        let rd_bits = reg_to_bits(rd);
6710        let base_bits = reg_to_bits(base);
6711        // STRH.W Rd, [Rn, #imm12]: 1111 1000 1010 Rn | Rt imm12
6712        check_ldst_imm12(offset)?;
6713        let hw1: u16 = (0xF8A0 | base_bits) as u16;
6714        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6715        let mut bytes = hw1.to_le_bytes().to_vec();
6716        bytes.extend_from_slice(&hw2.to_le_bytes());
6717        Ok(bytes)
6718    }
6719
6720    /// Encode Thumb-2 32-bit STRH with register: STRH.W Rd, [Rn, Rm]
6721    fn encode_thumb32_strh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6722        let rd_bits = reg_to_bits(rd);
6723        let base_bits = reg_to_bits(base);
6724        let rm_bits = reg_to_bits(offset_reg);
6725        // STRH.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0010 Rn | Rt 0000 00 imm2 Rm
6726        let hw1: u16 = (0xF820 | base_bits) as u16;
6727        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6728        let mut bytes = hw1.to_le_bytes().to_vec();
6729        bytes.extend_from_slice(&hw2.to_le_bytes());
6730        Ok(bytes)
6731    }
6732
6733    /// Encode Thumb-2 32-bit ADD with immediate: ADD.W Rd, Rn, #imm
6734    fn encode_thumb32_add_imm(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6735        let rd_bits = reg_to_bits(rd);
6736        let rn_bits = reg_to_bits(rn);
6737
6738        // For small immediates, use ADD.W Rd, Rn, #imm12
6739        // Encoding: 1111 0 i 0 1 0 0 0 S Rn | 0 imm3 Rd imm8
6740        // S = 0 (don't update flags)
6741        // The 12-bit immediate is encoded as: i:imm3:imm8
6742        // For simplicity, we only support imm <= 0xFFF (direct encoding)
6743        if imm <= 0xFFF {
6744            let i_bit = (imm >> 11) & 1;
6745            let imm3 = (imm >> 8) & 0x7;
6746            let imm8 = imm & 0xFF;
6747
6748            let hw1: u16 = (0xF100 | (i_bit << 10) | rn_bits) as u16;
6749            let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6750
6751            let mut bytes = hw1.to_le_bytes().to_vec();
6752            bytes.extend_from_slice(&hw2.to_le_bytes());
6753            Ok(bytes)
6754        } else {
6755            // Out-of-range immediate (> 0xFFF): materialize it into a scratch
6756            // register, then ADD.W Rd, Rn, scratch. This is the #180/#185
6757            // "encoder must produce a legal sequence, not assert" class — see #350.
6758            //
6759            // Scratch choice (must NEVER equal Rn, or Rn would be clobbered before
6760            // the ADD reads it):
6761            //   - rd != rn  => use rd itself (rn is untouched, since rd != rn).
6762            //   - rd == rn  => use R12/IP (the reserved encoder scratch). rd/rn are
6763            //                  never R12 (R12 is non-allocatable), so it can't alias.
6764            //
6765            // The materialized value is the same whether or not MOVT is emitted, so
6766            // the byte length depends only on `imm` (and rd==rn) — the size probe and
6767            // the final emit therefore agree (mandatory: the function is encoded twice).
6768            let scratch: u32 = if rd_bits == rn_bits {
6769                12 // R12/IP — in-place add, can't use rd because rd == rn
6770            } else {
6771                rd_bits // rn is preserved because rd != rn
6772            };
6773            // Invariant: the scratch must never alias Rn (would clobber it before
6774            // the ADD reads it). Unreachable in real codegen (rd/rn are never R12,
6775            // which is reserved encoder scratch), but the encoder is also driven by
6776            // the `encoder_no_panic` fuzz harness with ARBITRARY registers — incl.
6777            // rd==rn==R12, which makes scratch (R12) alias Rn. The encoder contract
6778            // (#180/#185) is Ok-or-Err, never a panic, so return a typed error
6779            // instead of asserting. #350 follow-up.
6780            if scratch == rn_bits {
6781                return Err(synth_core::Error::synthesis(format!(
6782                    "ADD #imm: cannot lower #{imm:#x} for Rd==Rn==R12 — no free scratch \
6783                     register (R12 is the reserved encoder scratch and aliases Rn here)"
6784                )));
6785            }
6786
6787            let lo16 = imm & 0xFFFF;
6788            let hi16 = (imm >> 16) & 0xFFFF;
6789
6790            let mut bytes = self.encode_thumb32_movw_raw(scratch, lo16)?;
6791            if hi16 != 0 {
6792                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(scratch, hi16)?);
6793            }
6794            bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(rd_bits, rn_bits, scratch)?);
6795            Ok(bytes)
6796        }
6797    }
6798
6799    // === Raw encoding helpers for POPCNT (take register numbers directly) ===
6800
6801    /// Encode Thumb-2 32-bit MOVW (16-bit immediate) - raw version
6802    ///
6803    /// # Contract (Verus-style)
6804    /// ```text
6805    /// requires rd <= 14, imm16 <= 0xFFFF
6806    /// ensures result.len() == 4
6807    /// ```
6808    fn encode_thumb32_movw_raw(&self, rd: u32, imm16: u32) -> Result<Vec<u8>> {
6809        reg_bits_checked(rd)?;
6810        encoding_contracts::verify_imm16(imm16);
6811        // MOVW Rd, #imm16
6812        // 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
6813        let imm16 = imm16 & 0xFFFF;
6814        let imm4 = (imm16 >> 12) & 0xF;
6815        let i_bit = (imm16 >> 11) & 1;
6816        let imm3 = (imm16 >> 8) & 0x7;
6817        let imm8 = imm16 & 0xFF;
6818
6819        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
6820        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6821
6822        let mut bytes = hw1.to_le_bytes().to_vec();
6823        bytes.extend_from_slice(&hw2.to_le_bytes());
6824        encoding_contracts::verify_thumb32(&bytes);
6825        Ok(bytes)
6826    }
6827
6828    /// Encode Thumb-2 32-bit MOVT (move top 16 bits) - raw version
6829    ///
6830    /// # Contract (Verus-style)
6831    /// ```text
6832    /// requires rd <= 14, imm16 <= 0xFFFF
6833    /// ensures result.len() == 4
6834    /// ```
6835    fn encode_thumb32_movt_raw(&self, rd: u32, imm16: u32) -> Result<Vec<u8>> {
6836        reg_bits_checked(rd)?;
6837        encoding_contracts::verify_imm16(imm16);
6838        // MOVT Rd, #imm16
6839        // 1111 0 i 10 1 1 0 0 imm4 | 0 imm3 Rd imm8
6840        let imm16 = imm16 & 0xFFFF;
6841        let imm4 = (imm16 >> 12) & 0xF;
6842        let i_bit = (imm16 >> 11) & 1;
6843        let imm3 = (imm16 >> 8) & 0x7;
6844        let imm8 = imm16 & 0xFF;
6845
6846        let hw1: u16 = (0xF2C0 | (i_bit << 10) | imm4) as u16;
6847        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6848
6849        let mut bytes = hw1.to_le_bytes().to_vec();
6850        bytes.extend_from_slice(&hw2.to_le_bytes());
6851        encoding_contracts::verify_thumb32(&bytes);
6852        Ok(bytes)
6853    }
6854
6855    /// Encode Thumb-2 32-bit LSR (logical shift right) with immediate - raw version
6856    fn encode_thumb32_lsr_raw(&self, rd: u32, rm: u32, shift: u32) -> Result<Vec<u8>> {
6857        // MOV.W Rd, Rm, LSR #imm
6858        // EA4F 0 imm3 Rd imm2 01 Rm
6859        let imm5 = shift & 0x1F;
6860        let imm2 = imm5 & 0x3;
6861        let imm3 = (imm5 >> 2) & 0x7;
6862
6863        let hw1: u16 = 0xEA4F;
6864        let hw2: u16 = ((imm3 << 12) | (rd << 8) | (imm2 << 6) | (0b01 << 4) | rm) as u16;
6865
6866        let mut bytes = hw1.to_le_bytes().to_vec();
6867        bytes.extend_from_slice(&hw2.to_le_bytes());
6868        Ok(bytes)
6869    }
6870
6871    /// Encode Thumb-2 32-bit AND (register) - raw version
6872    fn encode_thumb32_and_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6873        // AND.W Rd, Rn, Rm
6874        // EA00 Rn | 0 Rd 00 00 Rm
6875        let hw1: u16 = (0xEA00 | rn) as u16;
6876        let hw2: u16 = ((rd << 8) | rm) as u16;
6877
6878        let mut bytes = hw1.to_le_bytes().to_vec();
6879        bytes.extend_from_slice(&hw2.to_le_bytes());
6880        Ok(bytes)
6881    }
6882
6883    /// Encode Thumb-2 32-bit AND with immediate - raw version
6884    fn encode_thumb32_and_imm_raw(&self, rd: u32, rn: u32, imm: u32) -> Result<Vec<u8>> {
6885        // AND.W Rd, Rn, #<modified_immediate>
6886        // For small immediates (0-255), the encoding is simpler
6887        // F0 00 Rn | 0 imm3 Rd imm8
6888        let i_bit = (imm >> 11) & 1;
6889        let imm3 = (imm >> 8) & 0x7;
6890        let imm8 = imm & 0xFF;
6891
6892        let hw1: u16 = (0xF000 | (i_bit << 10) | rn) as u16;
6893        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6894
6895        let mut bytes = hw1.to_le_bytes().to_vec();
6896        bytes.extend_from_slice(&hw2.to_le_bytes());
6897        Ok(bytes)
6898    }
6899
6900    /// Encode Thumb-2 32-bit SUB (register) - raw version
6901    fn encode_thumb32_sub_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6902        // SUB.W Rd, Rn, Rm
6903        // EBA0 Rn | 0 Rd 00 00 Rm
6904        let hw1: u16 = (0xEBA0 | rn) as u16;
6905        let hw2: u16 = ((rd << 8) | rm) as u16;
6906
6907        let mut bytes = hw1.to_le_bytes().to_vec();
6908        bytes.extend_from_slice(&hw2.to_le_bytes());
6909        Ok(bytes)
6910    }
6911
6912    /// Encode Thumb-2 32-bit ADD (register) - raw version
6913    fn encode_thumb32_add_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6914        // ADD.W Rd, Rn, Rm
6915        // EB00 Rn | 0 Rd 00 00 Rm
6916        let hw1: u16 = (0xEB00 | rn) as u16;
6917        let hw2: u16 = ((rd << 8) | rm) as u16;
6918
6919        let mut bytes = hw1.to_le_bytes().to_vec();
6920        bytes.extend_from_slice(&hw2.to_le_bytes());
6921        Ok(bytes)
6922    }
6923
6924    /// Encode Thumb-2 32-bit ADDS (register, flag-setting) - raw version.
6925    /// Used as the high-register fallback for `ArmOp::Adds` (i64 low-word add)
6926    /// so R8-R11 pair operands don't overflow the 16-bit field — #178/#180.
6927    fn encode_thumb32_adds_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6928        // ADDS.W Rd, Rn, Rm (T3, S=1): EB10 Rn | 0 Rd 00 00 Rm
6929        let hw1: u16 = (0xEB10 | rn) as u16;
6930        let hw2: u16 = ((rd << 8) | rm) as u16;
6931        let mut bytes = hw1.to_le_bytes().to_vec();
6932        bytes.extend_from_slice(&hw2.to_le_bytes());
6933        Ok(bytes)
6934    }
6935
6936    /// Encode Thumb-2 32-bit SUBS (register, flag-setting) - raw version.
6937    /// High-register fallback for `ArmOp::Subs` (i64 low-word subtract) — #178/#180.
6938    fn encode_thumb32_subs_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6939        // SUBS.W Rd, Rn, Rm (T3, S=1): EBB0 Rn | 0 Rd 00 00 Rm
6940        let hw1: u16 = (0xEBB0 | rn) as u16;
6941        let hw2: u16 = ((rd << 8) | rm) as u16;
6942        let mut bytes = hw1.to_le_bytes().to_vec();
6943        bytes.extend_from_slice(&hw2.to_le_bytes());
6944        Ok(bytes)
6945    }
6946
6947    /// Encode a sequence of ARM instructions
6948    pub fn encode_sequence(&self, ops: &[ArmOp]) -> Result<Vec<u8>> {
6949        let mut code = Vec::new();
6950
6951        for op in ops {
6952            let encoded = self.encode(op)?;
6953            code.extend_from_slice(&encoded);
6954        }
6955
6956        Ok(code)
6957    }
6958}
6959
6960/// Convert register to bit encoding (0-15)
6961/// Reverse of the ARMv7-M `ThumbExpandImm`: given a 32-bit immediate, return the
6962/// 12-bit `i:imm3:imm8` field if it is a representable modified immediate, else
6963/// `None` (the caller must materialize the value into a register). This is the
6964/// shared correct path for the data-processing immediate encoders — without it
6965/// they pack raw bits and silently mis-encode any value `> 0xFF` that isn't a
6966/// modified immediate (the silent-miscompile class behind #251/#253/#255).
6967fn try_thumb_expand_imm(value: u32) -> Option<u32> {
6968    // i:imm3 = 0000 → 8-bit value, zero-extended (00000000 00000000 00000000 XY).
6969    if value <= 0xFF {
6970        return Some(value);
6971    }
6972    let b0 = value & 0xFF; // byte 0
6973    let b1 = (value >> 8) & 0xFF; // byte 1
6974    // 0x00XY00XY (i:imm3 = 0001) — XY in bytes 0 and 2
6975    if value == (b0 << 16) | b0 {
6976        return Some(0x100 | b0);
6977    }
6978    // 0xXY00XY00 (i:imm3 = 0010) — XY in bytes 1 and 3
6979    if value == (b1 << 24) | (b1 << 8) {
6980        return Some(0x200 | b1);
6981    }
6982    // 0xXYXYXYXY (i:imm3 = 0011) — XY in all four bytes
6983    if value == (b0 << 24) | (b0 << 16) | (b0 << 8) | b0 {
6984        return Some(0x300 | b0);
6985    }
6986    // An 8-bit value with bit 7 set, rotated right by 8..=31. `rotate_left(rot)`
6987    // undoes the encoded right rotation; if the result is `1bbbbbbb` (0x80..=0xFF)
6988    // the value is representable. imm12[11:7] = rot, imm12[6:0] = low 7 bits.
6989    for rot in 8..=31u32 {
6990        let unrot = value.rotate_left(rot);
6991        if (0x80..=0xFF).contains(&unrot) {
6992            return Some((rot << 7) | (unrot & 0x7F));
6993        }
6994    }
6995    None
6996}
6997
6998/// Guard a Thumb-2 `LDR/STR Rd, [Rn, #imm12]` offset. The imm12 form supports
6999/// `0..=4095`; a larger offset must be materialized into a register by the
7000/// selector (register-offset addressing). Returning `Err` rather than silently
7001/// masking `offset & 0xFFF` closes the wrong-address miscompile class (#259,
7002/// the load/store sibling of #253/#255).
7003fn check_ldst_imm12(offset: u32) -> Result<()> {
7004    if offset > 0xFFF {
7005        Err(synth_core::Error::synthesis(
7006            "load/store immediate offset > 0xFFF (4095) — materialize the offset into a register",
7007        ))
7008    } else {
7009        Ok(())
7010    }
7011}
7012
7013fn reg_to_bits(reg: &Reg) -> u32 {
7014    match reg {
7015        Reg::R0 => 0,
7016        Reg::R1 => 1,
7017        Reg::R2 => 2,
7018        Reg::R3 => 3,
7019        Reg::R4 => 4,
7020        Reg::R5 => 5,
7021        Reg::R6 => 6,
7022        Reg::R7 => 7,
7023        Reg::R8 => 8,
7024        Reg::R9 => 9,
7025        Reg::R10 => 10,
7026        Reg::R11 => 11,
7027        Reg::R12 => 12,
7028        Reg::SP => 13,
7029        Reg::LR => 14,
7030        Reg::PC => 15,
7031    }
7032}
7033
7034/// Fallible form of the `verify_reg_bits` contract. PC (R15) is not a valid
7035/// data operand for the Thumb-2 encodings that use this guard (SDIV/UDIV/MLS/…
7036/// are UNPREDICTABLE with PC). Synth's own codegen never emits PC there, but
7037/// the encoder must stay *total* over arbitrary `ArmOp` inputs — the fuzz
7038/// harness (`encoder_no_panic`) requires Ok-or-Err, never a panic. Pre-fix, the
7039/// `debug_assert` in `verify_reg_bits` aborted under `-Cdebug-assertions`.
7040/// Returns a typed Err instead. See #185.
7041fn reg_bits_checked(bits: u32) -> Result<()> {
7042    if bits > 14 {
7043        return Err(synth_core::Error::synthesis(format!(
7044            "register bits {bits} (PC/R15) is not a valid operand for this Thumb-2 encoding"
7045        )));
7046    }
7047    Ok(())
7048}
7049
7050/// Try to encode a 32-bit value as an ARM rotated immediate (imm8 ROR 2*rot4).
7051/// Returns Some((encoded_bits, 1)) if representable, None otherwise.
7052fn try_encode_rotated_imm(val: u32) -> Option<(u32, u32)> {
7053    if val == 0 {
7054        return Some((0, 1));
7055    }
7056    for rot in 0..16u32 {
7057        let shift = rot * 2;
7058        // Rotate left by shift (undo the ROR) to see if result fits in 8 bits
7059        let unrotated = val.rotate_left(shift);
7060        if unrotated <= 0xFF {
7061            // Encoded as: rot4(4 bits) | imm8(8 bits) = rotate_imm << 8 | imm8
7062            return Some(((rot << 8) | unrotated, 1));
7063        }
7064    }
7065    None
7066}
7067
7068/// Encode operand2 field and return (bits, immediate_flag).
7069/// For ARM32 mode, immediates use the rotated-immediate encoding (imm8 ROR 2*rot4).
7070/// Panics if an immediate value cannot be represented. Callers that need large
7071/// immediates should use MOVW/MOVT instead of Operand2::Imm.
7072fn encode_operand2(op2: &Operand2) -> Result<(u32, u32)> {
7073    match op2 {
7074        Operand2::Imm(val) => {
7075            let uval = *val as u32;
7076            // Attempt rotated-immediate encoding (ARM32 Operand2)
7077            if let Some(encoded) = try_encode_rotated_imm(uval) {
7078                Ok(encoded)
7079            } else {
7080                // #378-class honesty: an immediate that can't be expressed as an
7081                // ARM32 rotated immediate is an INTERNAL selector bug — large
7082                // constants must be materialized via MOVW/MOVT, not passed here.
7083                // FAIL HONESTLY with an Err rather than silently masking to
7084                // `uval & 0xFF` and emitting a WRONG immediate. The encoder is
7085                // Ok-or-Err, never corrupt (#180/#185); a loud Err is also why
7086                // this is an Err and not a panic (the `encoder_no_panic` fuzz
7087                // contract — malformed/oversized input must degrade, not crash).
7088                Err(synth_core::Error::synthesis(format!(
7089                    "encode_operand2: immediate {uval:#x} ({val}) is not an ARM32 \
7090                     rotated immediate — the selector must materialize large \
7091                     constants via MOVW/MOVT"
7092                )))
7093            }
7094        }
7095
7096        Operand2::Reg(reg) => {
7097            let reg_bits = reg_to_bits(reg);
7098            Ok((reg_bits, 0)) // I=0 for register
7099        }
7100
7101        Operand2::RegShift {
7102            rm,
7103            shift: _,
7104            amount,
7105        } => {
7106            // Simplified encoding with shift
7107            let rm_bits = reg_to_bits(rm);
7108            let shift_bits = (*amount & 0x1F) << 7;
7109            Ok((shift_bits | rm_bits, 0))
7110        }
7111    }
7112}
7113
7114/// Encode memory address to (base_reg, offset)
7115fn encode_mem_addr(addr: &MemAddr) -> (u32, u32) {
7116    let base_bits = reg_to_bits(&addr.base);
7117    let offset_bits = (addr.offset as u32) & 0xFFF; // 12-bit offset
7118    (base_bits, offset_bits)
7119}
7120
7121/// S-register number: S0=0, S1=1, ..., S31=31
7122fn vfp_sreg_to_num(reg: &VfpReg) -> Result<u32> {
7123    match reg {
7124        VfpReg::S0 => Ok(0),
7125        VfpReg::S1 => Ok(1),
7126        VfpReg::S2 => Ok(2),
7127        VfpReg::S3 => Ok(3),
7128        VfpReg::S4 => Ok(4),
7129        VfpReg::S5 => Ok(5),
7130        VfpReg::S6 => Ok(6),
7131        VfpReg::S7 => Ok(7),
7132        VfpReg::S8 => Ok(8),
7133        VfpReg::S9 => Ok(9),
7134        VfpReg::S10 => Ok(10),
7135        VfpReg::S11 => Ok(11),
7136        VfpReg::S12 => Ok(12),
7137        VfpReg::S13 => Ok(13),
7138        VfpReg::S14 => Ok(14),
7139        VfpReg::S15 => Ok(15),
7140        VfpReg::S16 => Ok(16),
7141        VfpReg::S17 => Ok(17),
7142        VfpReg::S18 => Ok(18),
7143        VfpReg::S19 => Ok(19),
7144        VfpReg::S20 => Ok(20),
7145        VfpReg::S21 => Ok(21),
7146        VfpReg::S22 => Ok(22),
7147        VfpReg::S23 => Ok(23),
7148        VfpReg::S24 => Ok(24),
7149        VfpReg::S25 => Ok(25),
7150        VfpReg::S26 => Ok(26),
7151        VfpReg::S27 => Ok(27),
7152        VfpReg::S28 => Ok(28),
7153        VfpReg::S29 => Ok(29),
7154        VfpReg::S30 => Ok(30),
7155        VfpReg::S31 => Ok(31),
7156        // D-registers are not used in F32 single-precision encodings
7157        _ => Err(synth_core::Error::SynthesisError(
7158            "D-register not supported in single-precision VFP encoding".to_string(),
7159        )),
7160    }
7161}
7162
7163/// D-register number: D0=0, D1=1, ..., D15=15
7164fn vfp_dreg_to_num(reg: &VfpReg) -> Result<u32> {
7165    match reg {
7166        VfpReg::D0 => Ok(0),
7167        VfpReg::D1 => Ok(1),
7168        VfpReg::D2 => Ok(2),
7169        VfpReg::D3 => Ok(3),
7170        VfpReg::D4 => Ok(4),
7171        VfpReg::D5 => Ok(5),
7172        VfpReg::D6 => Ok(6),
7173        VfpReg::D7 => Ok(7),
7174        VfpReg::D8 => Ok(8),
7175        VfpReg::D9 => Ok(9),
7176        VfpReg::D10 => Ok(10),
7177        VfpReg::D11 => Ok(11),
7178        VfpReg::D12 => Ok(12),
7179        VfpReg::D13 => Ok(13),
7180        VfpReg::D14 => Ok(14),
7181        VfpReg::D15 => Ok(15),
7182        // S-registers are not used in F64 double-precision encodings
7183        _ => Err(synth_core::Error::SynthesisError(
7184            "S-register not supported in double-precision VFP encoding".to_string(),
7185        )),
7186    }
7187}
7188
7189/// Split S-register into (Vx[3:0], qualifier_bit) for VFP encoding.
7190/// For an S-register number s: Vx = s >> 1, qualifier = s & 1.
7191/// The qualifier bit goes to D (bit 22), N (bit 7), or M (bit 5) depending on role.
7192fn encode_sreg(s: u32) -> (u32, u32) {
7193    (s >> 1, s & 1)
7194}
7195
7196/// Split D-register into (Vx[3:0], qualifier_bit) for VFP double-precision encoding.
7197/// For a D-register number d: Vx = d & 0xF, qualifier = (d >> 4) & 1.
7198/// For D0-D15, qualifier is always 0.
7199fn encode_dreg(d: u32) -> (u32, u32) {
7200    (d & 0xF, (d >> 4) & 1)
7201}
7202
7203/// Encode a VFP 3-register arithmetic instruction (VADD.F32, VSUB.F32, VMUL.F32, VDIV.F32).
7204/// Returns the full 32-bit instruction word.
7205///
7206/// VFP encoding: [cond 1110] [D opc1 Vn] [Vd 101 sz] [N opc2 M 0 Vm]
7207/// For single-precision (sz=0), coprocessor = 0xA (bits[11:8]).
7208fn encode_vfp_3reg(base: u32, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<u32> {
7209    let sd_num = vfp_sreg_to_num(sd)?;
7210    let sn_num = vfp_sreg_to_num(sn)?;
7211    let sm_num = vfp_sreg_to_num(sm)?;
7212    let (vd, d) = encode_sreg(sd_num);
7213    let (vn, n) = encode_sreg(sn_num);
7214    let (vm, m) = encode_sreg(sm_num);
7215
7216    Ok(base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm)
7217}
7218
7219/// Encode a VFP 2-register instruction (VNEG.F32, VABS.F32, VSQRT.F32).
7220/// Returns the full 32-bit instruction word.
7221fn encode_vfp_2reg(base: u32, sd: &VfpReg, sm: &VfpReg) -> Result<u32> {
7222    let sd_num = vfp_sreg_to_num(sd)?;
7223    let sm_num = vfp_sreg_to_num(sm)?;
7224    let (vd, d) = encode_sreg(sd_num);
7225    let (vm, m) = encode_sreg(sm_num);
7226
7227    Ok(base | (d << 22) | (vd << 12) | (m << 5) | vm)
7228}
7229
7230/// Encode a VFP load/store (VLDR.F32 / VSTR.F32).
7231/// offset is in bytes and must be word-aligned; encoded as imm8 = offset/4.
7232/// U bit (bit 23) controls add/subtract offset.
7233fn encode_vfp_ldst(base: u32, sd: &VfpReg, addr: &MemAddr) -> Result<u32> {
7234    let sd_num = vfp_sreg_to_num(sd)?;
7235    let (vd, d) = encode_sreg(sd_num);
7236    let rn = reg_to_bits(&addr.base);
7237
7238    let offset = addr.offset;
7239    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7240    let abs_offset = offset.unsigned_abs();
7241    let imm8 = (abs_offset / 4) & 0xFF;
7242
7243    Ok(base | (u_bit << 23) | (d << 22) | (rn << 16) | (vd << 12) | imm8)
7244}
7245
7246/// Encode VMOV between core register and S-register.
7247/// VMOV Sn, Rt: 0xEE00_0A10 | (Vn << 16) | (N << 7) | (Rt << 12)
7248/// VMOV Rt, Sn: 0xEE10_0A10 | (Vn << 16) | (N << 7) | (Rt << 12)
7249fn encode_vmov_core_sreg(to_sreg: bool, sreg: &VfpReg, core: &Reg) -> Result<u32> {
7250    let s_num = vfp_sreg_to_num(sreg)?;
7251    let (vn, n) = encode_sreg(s_num);
7252    let rt = reg_to_bits(core);
7253
7254    let base = if to_sreg { 0xEE000A10 } else { 0xEE100A10 };
7255    Ok(base | (vn << 16) | (rt << 12) | (n << 7))
7256}
7257
7258/// Encode a VFP 3-register double-precision instruction (VADD.F64, VSUB.F64, etc.).
7259/// For double-precision (sz=1), coprocessor = 0xB (bits[11:8]).
7260/// The base should have bit 8 = 1 for F64 (0xB suffix instead of 0xA).
7261fn encode_vfp_3reg_f64(base: u32, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<u32> {
7262    let dd_num = vfp_dreg_to_num(dd)?;
7263    let dn_num = vfp_dreg_to_num(dn)?;
7264    let dm_num = vfp_dreg_to_num(dm)?;
7265    let (vd, d) = encode_dreg(dd_num);
7266    let (vn, n) = encode_dreg(dn_num);
7267    let (vm, m) = encode_dreg(dm_num);
7268
7269    Ok(base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm)
7270}
7271
7272/// Encode a VFP 2-register double-precision instruction (VNEG.F64, VABS.F64, VSQRT.F64).
7273fn encode_vfp_2reg_f64(base: u32, dd: &VfpReg, dm: &VfpReg) -> Result<u32> {
7274    let dd_num = vfp_dreg_to_num(dd)?;
7275    let dm_num = vfp_dreg_to_num(dm)?;
7276    let (vd, d) = encode_dreg(dd_num);
7277    let (vm, m) = encode_dreg(dm_num);
7278
7279    Ok(base | (d << 22) | (vd << 12) | (m << 5) | vm)
7280}
7281
7282/// Encode a VFP load/store for double-precision (VLDR.64 / VSTR.64).
7283/// offset is in bytes and must be word-aligned; encoded as imm8 = offset/4.
7284fn encode_vfp_ldst_f64(base: u32, dd: &VfpReg, addr: &MemAddr) -> Result<u32> {
7285    let dd_num = vfp_dreg_to_num(dd)?;
7286    let (vd, d) = encode_dreg(dd_num);
7287    let rn = reg_to_bits(&addr.base);
7288
7289    let offset = addr.offset;
7290    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7291    let abs_offset = offset.unsigned_abs();
7292    let imm8 = (abs_offset / 4) & 0xFF;
7293
7294    Ok(base | (u_bit << 23) | (d << 22) | (rn << 16) | (vd << 12) | imm8)
7295}
7296
7297/// Encode VMOV between two core registers and a D-register.
7298/// VMOV Dm, Rt, Rt2: 0xEC40_0B10 | (Rt2 << 16) | (Rt << 12) | (M << 5) | Vm
7299/// VMOV Rt, Rt2, Dm: 0xEC50_0B10 | (Rt2 << 16) | (Rt << 12) | (M << 5) | Vm
7300fn encode_vmov_core_dreg(
7301    to_dreg: bool,
7302    dreg: &VfpReg,
7303    core_lo: &Reg,
7304    core_hi: &Reg,
7305) -> Result<u32> {
7306    let d_num = vfp_dreg_to_num(dreg)?;
7307    let (vm, m) = encode_dreg(d_num);
7308    let rt = reg_to_bits(core_lo);
7309    let rt2 = reg_to_bits(core_hi);
7310
7311    let base = if to_dreg { 0xEC400B10 } else { 0xEC500B10 };
7312    Ok(base | (rt2 << 16) | (rt << 12) | (m << 5) | vm)
7313}
7314
7315/// Emit a VFP 32-bit instruction as Thumb-2 bytes (two LE halfwords).
7316fn vfp_to_thumb_bytes(instr: u32) -> Vec<u8> {
7317    let hw1 = ((instr >> 16) & 0xFFFF) as u16;
7318    let hw2 = (instr & 0xFFFF) as u16;
7319    let mut bytes = hw1.to_le_bytes().to_vec();
7320    bytes.extend_from_slice(&hw2.to_le_bytes());
7321    bytes
7322}
7323
7324// ============================================================================
7325// Helium MVE encoding helpers
7326// ============================================================================
7327
7328/// Q-register number: Q0=0, Q1=1, ..., Q7=7
7329fn qreg_to_num(reg: &QReg) -> u32 {
7330    match reg {
7331        QReg::Q0 => 0,
7332        QReg::Q1 => 1,
7333        QReg::Q2 => 2,
7334        QReg::Q3 => 3,
7335        QReg::Q4 => 4,
7336        QReg::Q5 => 5,
7337        QReg::Q6 => 6,
7338        QReg::Q7 => 7,
7339    }
7340}
7341
7342/// MVE element size to encoding bits: S8=0b00, S16=0b01, S32=0b10
7343fn mve_size_bits(size: &MveSize) -> u32 {
7344    match size {
7345        MveSize::S8 => 0b00,
7346        MveSize::S16 => 0b01,
7347        MveSize::S32 => 0b10,
7348    }
7349}
7350
7351/// Encode MVE 3-register instruction.
7352/// Q-registers are encoded as D-register pairs: Q0=D0:D1, Q1=D2:D3, etc.
7353/// In NEON/MVE encoding, the Q-register uses D-register number = Qn * 2.
7354fn encode_mve_3reg(base: u32, qd: &QReg, qn: &QReg, qm: &QReg) -> u32 {
7355    let d = qreg_to_num(qd) * 2;
7356    let n = qreg_to_num(qn) * 2;
7357    let m = qreg_to_num(qm) * 2;
7358
7359    // Standard NEON/MVE 3-register encoding:
7360    // D bit (bit 22) = Vd[4], Vd[3:0] = bits [15:12]
7361    // N bit (bit 7)  = Vn[4], Vn[3:0] = bits [19:16]
7362    // M bit (bit 5)  = Vm[4], Vm[3:0] = bits [3:0]
7363    let vd = d & 0xF;
7364    let d_bit = (d >> 4) & 1;
7365    let vn = n & 0xF;
7366    let n_bit = (n >> 4) & 1;
7367    let vm = m & 0xF;
7368    let m_bit = (m >> 4) & 1;
7369
7370    base | (d_bit << 22) | (vn << 16) | (vd << 12) | (n_bit << 7) | (m_bit << 5) | vm
7371}
7372
7373/// Encode MVE 3-register bitwise instruction (VAND, VORR, VEOR, VBIC).
7374fn encode_mve_3reg_bitwise(base: u32, qd: &QReg, qn: &QReg, qm: &QReg) -> u32 {
7375    encode_mve_3reg(base, qd, qn, qm)
7376}
7377
7378/// Encode MVE VLDRW.32 Qd, [Rn, #offset]
7379/// Format: EC9x xxxx - contiguous load, word-sized elements
7380fn encode_mve_vldrw(qd: &QReg, addr: &MemAddr) -> u32 {
7381    let qd_enc = qreg_to_num(qd) * 2;
7382    let rn = reg_to_bits(&addr.base);
7383    let offset = addr.offset;
7384    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7385    let abs_offset = offset.unsigned_abs();
7386    let imm7 = (abs_offset / 4) & 0x7F; // 7-bit word-aligned offset
7387
7388    // VLDRW.32 Qd, [Rn, #imm]: ED10 xx80 variant
7389    0xED100E80
7390        | (u_bit << 23)
7391        | ((qd_enc >> 4) << 22)
7392        | (rn << 16)
7393        | ((qd_enc & 0xF) << 12)
7394        | (imm7 & 0x7F)
7395}
7396
7397/// Encode MVE VSTRW.32 Qd, [Rn, #offset]
7398fn encode_mve_vstrw(qd: &QReg, addr: &MemAddr) -> u32 {
7399    let qd_enc = qreg_to_num(qd) * 2;
7400    let rn = reg_to_bits(&addr.base);
7401    let offset = addr.offset;
7402    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7403    let abs_offset = offset.unsigned_abs();
7404    let imm7 = (abs_offset / 4) & 0x7F;
7405
7406    0xED000E80
7407        | (u_bit << 23)
7408        | ((qd_enc >> 4) << 22)
7409        | (rn << 16)
7410        | ((qd_enc & 0xF) << 12)
7411        | (imm7 & 0x7F)
7412}
7413
7414impl ArmEncoder {
7415    /// Encode MVE constant load: MOVW+MOVT+VMOV for each 32-bit word, then assemble Q-register
7416    fn encode_thumb_mve_const(&self, qd: &QReg, bytes: &[u8; 16]) -> Result<Vec<u8>> {
7417        let mut result = Vec::new();
7418        let qd_num = qreg_to_num(qd);
7419
7420        // Load each 32-bit word into R12 (temp) then VMOV into S-register
7421        for i in 0..4 {
7422            let word = u32::from_le_bytes([
7423                bytes[i * 4],
7424                bytes[i * 4 + 1],
7425                bytes[i * 4 + 2],
7426                bytes[i * 4 + 3],
7427            ]);
7428            let lo16 = word & 0xFFFF;
7429            let hi16 = (word >> 16) & 0xFFFF;
7430
7431            // MOVW R12, #lo16
7432            result.extend_from_slice(&self.encode_thumb32_movw_raw(12, lo16)?);
7433            // MOVT R12, #hi16
7434            if hi16 != 0 {
7435                result.extend_from_slice(&self.encode_thumb32_movt_raw(12, hi16)?);
7436            }
7437
7438            // VMOV Sn, R12 where Sn = Qd*4 + i
7439            let s_num = qd_num * 4 + i as u32;
7440            let (vn, n) = encode_sreg(s_num);
7441            let vmov: u32 = 0xEE000A10 | (vn << 16) | (12 << 12) | (n << 7);
7442            result.extend_from_slice(&vfp_to_thumb_bytes(vmov));
7443        }
7444
7445        Ok(result)
7446    }
7447
7448    /// Encode lane-wise f32 binary operation (VDIV, etc.) via S-register extraction
7449    fn encode_thumb_mve_lane_wise_f32_binop(
7450        &self,
7451        qd: &QReg,
7452        qn: &QReg,
7453        qm: &QReg,
7454        vfp_base: u32,
7455    ) -> Result<Vec<u8>> {
7456        let mut result = Vec::new();
7457        let qd_num = qreg_to_num(qd);
7458        let qn_num = qreg_to_num(qn);
7459        let qm_num = qreg_to_num(qm);
7460
7461        // For each lane 0..3: use S-registers directly (Q aliasing)
7462        for i in 0..4u32 {
7463            let sd = qd_num * 4 + i;
7464            let sn = qn_num * 4 + i;
7465            let sm = qm_num * 4 + i;
7466
7467            let (vd, d) = encode_sreg(sd);
7468            let (vn, n) = encode_sreg(sn);
7469            let (vm, m) = encode_sreg(sm);
7470
7471            let instr = vfp_base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm;
7472            result.extend_from_slice(&vfp_to_thumb_bytes(instr));
7473        }
7474
7475        Ok(result)
7476    }
7477
7478    /// Encode lane-wise f32 VSQRT via S-register extraction
7479    fn encode_thumb_mve_lane_wise_f32_sqrt(&self, qd: &QReg, qm: &QReg) -> Result<Vec<u8>> {
7480        let mut result = Vec::new();
7481        let qd_num = qreg_to_num(qd);
7482        let qm_num = qreg_to_num(qm);
7483
7484        // VSQRT.F32 base: 0xEEB10AC0
7485        for i in 0..4u32 {
7486            let sd = qd_num * 4 + i;
7487            let sm = qm_num * 4 + i;
7488
7489            let (vd, d) = encode_sreg(sd);
7490            let (vm, m) = encode_sreg(sm);
7491
7492            let instr: u32 = 0xEEB10AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
7493            result.extend_from_slice(&vfp_to_thumb_bytes(instr));
7494        }
7495
7496        Ok(result)
7497    }
7498}
7499
7500#[cfg(test)]
7501mod tests {
7502    use super::*;
7503
7504    #[test]
7505    fn test_encoder_creation() {
7506        let encoder_arm = ArmEncoder::new_arm32();
7507        assert!(!encoder_arm.thumb_mode);
7508
7509        let encoder_thumb = ArmEncoder::new_thumb2();
7510        assert!(encoder_thumb.thumb_mode);
7511    }
7512
7513    /// #204 WAKE-path regression: `SetCond` materialized 0/1 with the 16-bit
7514    /// `MOVS Rd,#imm` (T1), whose Rd field is 3 bits (R0–R7). For a high Rd
7515    /// (R8–R12) `rd_bits << 8` overflows bit 11, flipping the opcode MOVS→CMP
7516    /// (`0x2c00`), so the boolean was never written — gale's `has_waiter` kept a
7517    /// stale value and the binary-sem WAKE dispatch read garbage. High Rd must
7518    /// use the 32-bit `MOV.W` (T2). Verify the bytes, not the IR.
7519    /// #311: the SAME high-Rd MOVS→CMP transmutation as #204, but in the
7520    /// i64 comparison expansions (I64SetCond / I64SetCondZ) — missed by the
7521    /// #204 hardening. With rd=R8 the boolean died in the flags
7522    /// (`ite eq; cmpeq r0,#1; cmpne r0,#0`), so gale's packed-u64 select
7523    /// read a stale register on silicon. High Rd must take MOV.W / CMP.W.
7524    #[test]
7525    fn test_encode_i64setcond_high_reg_uses_mov_w_311() {
7526        use synth_synthesis::{ArmOp, Condition, Reg};
7527        let enc = ArmEncoder::new_thumb2();
7528        let bytes = enc
7529            .encode(&ArmOp::I64SetCond {
7530                rd: Reg::R8,
7531                rn_lo: Reg::R2,
7532                rn_hi: Reg::R3,
7533                rm_lo: Reg::R6,
7534                rm_hi: Reg::R7,
7535                cond: Condition::EQ,
7536            })
7537            .unwrap();
7538        // The 32-bit MOV.W immediate (T2) first halfword is 0xF04F; the
7539        // 16-bit transmuted forms would contain 0x2801/0x2800 (CMP r0,#1/#0).
7540        let halfwords: Vec<u16> = bytes
7541            .chunks(2)
7542            .map(|c| u16::from_le_bytes([c[0], c[1]]))
7543            .collect();
7544        assert!(
7545            halfwords.iter().filter(|&&h| h == 0xF04F).count() == 2,
7546            "high rd must use two MOV.W (T2) encodings, got {halfwords:04x?}"
7547        );
7548        assert!(
7549            !halfwords.contains(&0x2801) && !halfwords.contains(&0x2800),
7550            "no transmuted 16-bit CMP imm: {halfwords:04x?}"
7551        );
7552
7553        let bytes_z = enc
7554            .encode(&ArmOp::I64SetCondZ {
7555                rd: Reg::R8,
7556                rn_lo: Reg::R2,
7557                rn_hi: Reg::R3,
7558            })
7559            .unwrap();
7560        let hw_z: Vec<u16> = bytes_z
7561            .chunks(2)
7562            .map(|c| u16::from_le_bytes([c[0], c[1]]))
7563            .collect();
7564        assert!(
7565            hw_z.iter().filter(|&&h| h == 0xF04F).count() == 2,
7566            "SetCondZ high rd MOV.W: {hw_z:04x?}"
7567        );
7568        // CMP.W rd,#0 (T2) first halfword: 0xF1B0 | rd
7569        assert!(
7570            hw_z.contains(&(0xF1B0 | 8)),
7571            "SetCondZ high rd must use CMP.W: {hw_z:04x?}"
7572        );
7573    }
7574
7575    #[test]
7576    fn test_encode_setcond_high_reg_uses_mov_w_204() {
7577        use synth_synthesis::{ArmOp, Condition, Reg};
7578        let enc = ArmEncoder::new_thumb2();
7579        // R12 (high): must be ITE + MOV.W #1 + MOV.W #0, never a 16-bit MOVS/CMP.
7580        let hi = enc
7581            .encode(&ArmOp::SetCond {
7582                rd: Reg::R12,
7583                cond: Condition::NE,
7584            })
7585            .unwrap();
7586        assert_eq!(hi.len(), 10, "ITE(2) + MOV.W(4) + MOV.W(4): {hi:02x?}");
7587        // both value halfwords are MOV.W (0xF04F) — NOT the corrupt CMP (0x2c..).
7588        assert_eq!(&hi[2..4], &[0x4F, 0xF0], "then = MOV.W: {hi:02x?}");
7589        assert_eq!(&hi[6..8], &[0x4F, 0xF0], "else = MOV.W: {hi:02x?}");
7590        assert_eq!(hi[4] & 0x0F, 0x01, "then imm = #1");
7591        assert_eq!(hi[8] & 0x0F, 0x00, "else imm = #0");
7592        // Low Rd keeps the compact 16-bit MOVS form.
7593        let lo = enc
7594            .encode(&ArmOp::SetCond {
7595                rd: Reg::R0,
7596                cond: Condition::NE,
7597            })
7598            .unwrap();
7599        assert_eq!(lo.len(), 6, "ITE(2) + MOVS(2) + MOVS(2): {lo:02x?}");
7600        assert_eq!(lo[2..4], [0x01, 0x20], "then = MOVS R0,#1");
7601        assert_eq!(lo[4..6], [0x00, 0x20], "else = MOVS R0,#0");
7602    }
7603
7604    /// #209 Opt 1b: UMULL RdLo, RdHi, Rn, Rm encodes correctly on both ISAs.
7605    /// Thumb-2 T1: 1111 1011 1010 Rn | RdLo RdHi 0000 Rm.
7606    /// A32:        cond 0000 1000 RdHi RdLo Rm 1001 Rn.
7607    #[test]
7608    fn test_encode_umull_209b() {
7609        use synth_synthesis::{ArmOp, Reg};
7610        let op = ArmOp::Umull {
7611            rdlo: Reg::R4,
7612            rdhi: Reg::R5,
7613            rn: Reg::R0,
7614            rm: Reg::R3,
7615        };
7616        // Thumb-2: hw1 = 0xFBA0 | 0 = 0xFBA0; hw2 = (4<<12)|(5<<8)|3 = 0x4503.
7617        let t = ArmEncoder::new_thumb2().encode(&op).unwrap();
7618        assert_eq!(
7619            t,
7620            vec![0xA0, 0xFB, 0x03, 0x45],
7621            "umull r4,r5,r0,r3 (T2): {t:02x?}"
7622        );
7623        // A32: 0xE0800090 | (5<<16) | (4<<12) | (3<<8) | 0 = 0xE0854390.
7624        let a = ArmEncoder::new_arm32().encode(&op).unwrap();
7625        assert_eq!(
7626            a,
7627            0xE085_4390u32.to_le_bytes().to_vec(),
7628            "umull (A32): {a:02x?}"
7629        );
7630    }
7631
7632    /// #206 regression: the ARM32 (A32) `Ldr`/`Str` encoders fed `addr` through
7633    /// `encode_mem_addr`, which returns only the 12-bit immediate — so a register
7634    /// offset (`[rn, rm, #off]`) was silently dropped to `[rn, #off]`, sending
7635    /// the access to the wrong runtime address (silent miscompile on the default
7636    /// `--target arm`). A register offset must materialize `ip = rn + rm` and
7637    /// load from `[ip, #off]`. Verify the bytes.
7638    #[test]
7639    fn test_encode_arm32_indexed_load_keeps_index_206() {
7640        use synth_synthesis::{ArmOp, MemAddr, Reg};
7641        let enc = ArmEncoder::new_arm32();
7642        // ldr r0, [r11, r1, #8]  must NOT collapse to a single immediate ldr.
7643        let bytes = enc
7644            .encode(&ArmOp::Ldr {
7645                rd: Reg::R0,
7646                addr: MemAddr::reg_imm(Reg::R11, Reg::R1, 8),
7647            })
7648            .unwrap();
7649        assert_eq!(
7650            bytes.len(),
7651            8,
7652            "expected ADD ip + LDR (2 words): {bytes:02x?}"
7653        );
7654        let add = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
7655        let ldr = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
7656        // ADD ip, r11, r1  = 0xE08BC001
7657        assert_eq!(add, 0xE08B_C001, "ADD ip,r11,r1: {add:#010x}");
7658        // LDR r0, [ip, #8] = 0xE59C0008
7659        assert_eq!(ldr, 0xE59C_0008, "LDR r0,[ip,#8]: {ldr:#010x}");
7660        // A bare immediate ldr (the bug) would be 0xE59B0008 (base=r11) — reject.
7661        assert_ne!(ldr, 0xE59B_0008, "index must not be dropped");
7662    }
7663
7664    /// #594 regression: `call_indirect` on the A32 path (`--target cortex-r5`)
7665    /// was encoded as a literal NOP (0xE1A00000) — the call never happened and
7666    /// the function silently returned the leftover table-index value. The A32
7667    /// encoder must emit the same three-instruction expansion as Thumb-2:
7668    /// `MOV r12, idx, LSL #2; LDR r12, [r11, r12]; BLX r12`.
7669    #[test]
7670    fn test_encode_arm32_call_indirect_is_real_call_594() {
7671        use synth_synthesis::{ArmOp, Reg};
7672        let enc = ArmEncoder::new_arm32();
7673        let bytes = enc
7674            .encode(&ArmOp::CallIndirect {
7675                rd: Reg::R0,
7676                type_idx: 0,
7677                table_index_reg: Reg::R0,
7678            })
7679            .unwrap();
7680        assert_eq!(
7681            bytes.len(),
7682            12,
7683            "expected MOV + LDR + BLX (3 words): {bytes:02x?}"
7684        );
7685        let mov = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
7686        let ldr = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
7687        let blx = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
7688        // MOV r12, r0, LSL #2 = 0xE1A0C100
7689        assert_eq!(mov, 0xE1A0_C100, "MOV r12,r0,LSL#2: {mov:#010x}");
7690        // LDR r12, [r11, r12] = 0xE79BC00C
7691        assert_eq!(ldr, 0xE79B_C00C, "LDR r12,[r11,r12]: {ldr:#010x}");
7692        // BLX r12 = 0xE12FFF3C
7693        assert_eq!(blx, 0xE12F_FF3C, "BLX r12: {blx:#010x}");
7694        // The bug: a single NOP word. Must never come back.
7695        assert!(
7696            !bytes
7697                .chunks_exact(4)
7698                .any(|w| w == 0xE1A0_0000u32.to_le_bytes()),
7699            "call_indirect must not contain a NOP (#594): {bytes:02x?}"
7700        );
7701
7702        // A non-R0 index register lands in the MOV's Rm field.
7703        let bytes = enc
7704            .encode(&ArmOp::CallIndirect {
7705                rd: Reg::R0,
7706                type_idx: 0,
7707                table_index_reg: Reg::R4,
7708            })
7709            .unwrap();
7710        let mov = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
7711        assert_eq!(mov, 0xE1A0_C104, "MOV r12,r4,LSL#2: {mov:#010x}");
7712    }
7713
7714    /// #597 anchor (justified correctness RE-PIN of the #594-era freeze): the
7715    /// Thumb-2 `CallIndirect` expansion is `mov.w ip, rm, LSL #2; ldr.w ip,
7716    /// [r11, ip]; blx ip`.
7717    ///
7718    /// The #594 PR froze the then-current bytes `4F EA 20 0C ...` whose first
7719    /// word decodes as `mov.w ip, rm, ASR #32` — the intended `LSL #2` had
7720    /// its shift amount in the TYPE field (bits 5:4) instead of imm2 (bits
7721    /// 7:6), so the index was destroyed and every call_indirect dispatched
7722    /// table entry 0 (shipped miscompile, masked by index-0 probes). #597
7723    /// corrects the encoding; new bytes `4F EA 80 0C ...` were
7724    /// execution-validated under unicorn against the wasmtime oracle on a
7725    /// multi-entry table (indexes 0, 1, 3 —
7726    /// scripts/repro/call_indirect_597_differential.py) before this pin was
7727    /// replaced. Old pin: [4F EA 20 0C, 5B F8 0C C0, E0 47] (ASR #32 — must
7728    /// never come back).
7729    #[test]
7730    fn test_encode_thumb_call_indirect_lsl2_597() {
7731        use synth_synthesis::{ArmOp, Reg};
7732        let enc = ArmEncoder::new_thumb2();
7733        let bytes = enc
7734            .encode(&ArmOp::CallIndirect {
7735                rd: Reg::R0,
7736                type_idx: 0,
7737                table_index_reg: Reg::R0,
7738            })
7739            .unwrap();
7740        assert_eq!(
7741            bytes,
7742            vec![0x4F, 0xEA, 0x80, 0x0C, 0x5B, 0xF8, 0x0C, 0xC0, 0xE0, 0x47],
7743            "Thumb-2 CallIndirect: mov.w ip,r0,LSL#2; ldr.w ip,[r11,ip]; blx ip: {bytes:02x?}"
7744        );
7745        // The #597 bug bytes (ASR #32 first word) must never come back.
7746        assert_ne!(
7747            &bytes[0..4],
7748            &[0x4F, 0xEA, 0x20, 0x0C],
7749            "mov.w ip, rm, ASR #32 — the #597 type-field bug"
7750        );
7751
7752        // A non-R0 index register lands in the mov.w's Rm field (hw2 bits 3:0).
7753        let bytes = enc
7754            .encode(&ArmOp::CallIndirect {
7755                rd: Reg::R0,
7756                type_idx: 0,
7757                table_index_reg: Reg::R4,
7758            })
7759            .unwrap();
7760        assert_eq!(
7761            &bytes[0..4],
7762            &[0x4F, 0xEA, 0x84, 0x0C],
7763            "mov.w ip, r4, LSL #2: {bytes:02x?}"
7764        );
7765    }
7766
7767    /// #178/#180 regression: the Thumb `Add`/`Adds`/`Subs` reg-forms used the
7768    /// 16-bit encoding unconditionally. For high registers (R12 base scratch,
7769    /// R8-R11 i64 pairs) the 3-bit register fields overflow and corrupt the
7770    /// operands — `add ip,ip,r0` came out as `adds r4,r5,r1` (0x186C), silently
7771    /// dropping the address operand and miscompiling every optimized memory
7772    /// access. High registers must use the 32-bit `.W` forms.
7773    #[test]
7774    fn test_encode_thumb_add_high_reg_uses_add_w_178_180() {
7775        let encoder = ArmEncoder::new_thumb2();
7776
7777        // add ip, ip, r0  — the exact MemLoad/MemStore base+addr op.
7778        let code = encoder
7779            .encode(&ArmOp::Add {
7780                rd: Reg::R12,
7781                rn: Reg::R12,
7782                op2: Operand2::Reg(Reg::R0),
7783            })
7784            .unwrap();
7785        // ADD.W ip, ip, r0 = EB0C 0C00 (little-endian halfwords).
7786        assert_eq!(
7787            code,
7788            vec![0x0C, 0xEB, 0x00, 0x0C],
7789            "high-reg Thumb ADD must be 32-bit ADD.W (EB0C 0C00), not corrupt 16-bit; got {code:02X?}"
7790        );
7791        // Must NOT be the buggy 16-bit 0x186C (`adds r4,r5,r1`).
7792        assert_ne!(code, vec![0x6C, 0x18], "regressed to corrupt 16-bit ADDS");
7793
7794        // Low-register add stays 16-bit (no regression for the common case).
7795        let lo = encoder
7796            .encode(&ArmOp::Add {
7797                rd: Reg::R1,
7798                rn: Reg::R2,
7799                op2: Operand2::Reg(Reg::R3),
7800            })
7801            .unwrap();
7802        assert_eq!(
7803            lo.len(),
7804            2,
7805            "low-reg ADD should remain 16-bit, got {lo:02X?}"
7806        );
7807    }
7808
7809    /// #178/#180 sibling: i64 low-word `Adds`/`Subs` can land in R8-R11 pairs;
7810    /// those must fall back to 32-bit ADDS.W/SUBS.W (flag-setting preserved).
7811    #[test]
7812    fn test_encode_thumb_adds_subs_high_reg_use_32bit_178_180() {
7813        let encoder = ArmEncoder::new_thumb2();
7814
7815        // adds r10, r10, r8  → ADDS.W = EB1A 0A08
7816        let adds = encoder
7817            .encode(&ArmOp::Adds {
7818                rd: Reg::R10,
7819                rn: Reg::R10,
7820                op2: Operand2::Reg(Reg::R8),
7821            })
7822            .unwrap();
7823        assert_eq!(
7824            adds,
7825            vec![0x1A, 0xEB, 0x08, 0x0A],
7826            "high-reg ADDS must be 32-bit ADDS.W (EB1A 0A08); got {adds:02X?}"
7827        );
7828
7829        // subs r10, r10, r8  → SUBS.W = EBBA 0A08
7830        let subs = encoder
7831            .encode(&ArmOp::Subs {
7832                rd: Reg::R10,
7833                rn: Reg::R10,
7834                op2: Operand2::Reg(Reg::R8),
7835            })
7836            .unwrap();
7837        assert_eq!(
7838            subs,
7839            vec![0xBA, 0xEB, 0x08, 0x0A],
7840            "high-reg SUBS must be 32-bit SUBS.W (EBBA 0A08); got {subs:02X?}"
7841        );
7842    }
7843
7844    /// #184 (sibling of #180): 16-bit CMN (T1) only encodes R0-R7. High registers
7845    /// must use 32-bit CMN.W, not the corrupt truncated 16-bit form.
7846    #[test]
7847    fn test_encode_thumb_cmn_high_reg_uses_cmn_w_184() {
7848        let encoder = ArmEncoder::new_thumb2();
7849
7850        // cmn r10, r8  → CMN.W = EB1A 0F08 (ADD.W S=1, Rd=PC discarded).
7851        let cmn = encoder
7852            .encode(&ArmOp::Cmn {
7853                rn: Reg::R10,
7854                op2: Operand2::Reg(Reg::R8),
7855            })
7856            .unwrap();
7857        assert_eq!(
7858            cmn,
7859            vec![0x1A, 0xEB, 0x08, 0x0F],
7860            "high-reg CMN must be 32-bit CMN.W (EB1A 0F08); got {cmn:02X?}"
7861        );
7862
7863        // Low registers stay 16-bit: cmn r1, r2 = 0x42D1.
7864        let lo = encoder
7865            .encode(&ArmOp::Cmn {
7866                rn: Reg::R1,
7867                op2: Operand2::Reg(Reg::R2),
7868            })
7869            .unwrap();
7870        assert_eq!(
7871            lo.len(),
7872            2,
7873            "low-reg CMN should remain 16-bit, got {lo:02X?}"
7874        );
7875        assert_eq!(lo, vec![0xD1, 0x42], "low-reg CMN bytes wrong: {lo:02X?}");
7876    }
7877
7878    /// #185 regression: feeding PC (R15) as a data operand to a Thumb-2 op that
7879    /// guards its registers must return Err, not panic under debug-assertions.
7880    /// (Synth never emits PC here; the fuzz harness requires encode() be total.)
7881    #[test]
7882    fn test_encode_pc_operand_returns_err_not_panic_185() {
7883        let encoder = ArmEncoder::new_thumb2();
7884        for op in [
7885            ArmOp::Sdiv {
7886                rd: Reg::PC,
7887                rn: Reg::R0,
7888                rm: Reg::R1,
7889            },
7890            ArmOp::Udiv {
7891                rd: Reg::R0,
7892                rn: Reg::PC,
7893                rm: Reg::R1,
7894            },
7895            ArmOp::Sdiv {
7896                rd: Reg::R0,
7897                rn: Reg::R1,
7898                rm: Reg::PC,
7899            },
7900        ] {
7901            let r = encoder.encode(&op);
7902            assert!(
7903                r.is_err(),
7904                "encode({op:?}) must return Err for a PC operand, got {r:?}"
7905            );
7906        }
7907        // Valid registers still encode fine (no false rejection).
7908        assert!(
7909            encoder
7910                .encode(&ArmOp::Sdiv {
7911                    rd: Reg::R0,
7912                    rn: Reg::R1,
7913                    rm: Reg::R2
7914                })
7915                .is_ok()
7916        );
7917    }
7918
7919    #[test]
7920    fn test_encode_nop_arm32() {
7921        let encoder = ArmEncoder::new_arm32();
7922        let code = encoder.encode(&ArmOp::Nop).unwrap();
7923
7924        assert_eq!(code.len(), 4); // ARM32 instructions are 4 bytes
7925        assert_eq!(code, vec![0x00, 0x00, 0xA0, 0xE1]); // MOV R0, R0
7926    }
7927
7928    #[test]
7929    fn test_encode_nop_thumb() {
7930        let encoder = ArmEncoder::new_thumb2();
7931        let code = encoder.encode(&ArmOp::Nop).unwrap();
7932
7933        assert_eq!(code.len(), 2); // Thumb instructions are 2 bytes
7934        assert_eq!(code, vec![0x00, 0xBF]); // NOP
7935    }
7936
7937    #[test]
7938    fn test_encode_mov_immediate_arm32() {
7939        let encoder = ArmEncoder::new_arm32();
7940        let op = ArmOp::Mov {
7941            rd: Reg::R0,
7942            op2: Operand2::Imm(42),
7943        };
7944
7945        let code = encoder.encode(&op).unwrap();
7946        assert_eq!(code.len(), 4);
7947
7948        // Verify it's a MOV instruction (bits should have immediate flag set)
7949        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7950        assert_eq!(instr & 0x0E000000, 0x02000000); // Check I bit is set
7951    }
7952
7953    #[test]
7954    fn test_encode_add_registers_arm32() {
7955        let encoder = ArmEncoder::new_arm32();
7956        let op = ArmOp::Add {
7957            rd: Reg::R0,
7958            rn: Reg::R1,
7959            op2: Operand2::Reg(Reg::R2),
7960        };
7961
7962        let code = encoder.encode(&op).unwrap();
7963        assert_eq!(code.len(), 4);
7964
7965        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7966        // Verify it's an ADD instruction with correct opcode
7967        assert_eq!(instr & 0x0FE00000, 0x00800000);
7968    }
7969
7970    /// #350 — `encode_thumb32_add_imm` must lower an out-of-range immediate
7971    /// (> 0xFFF) to a legal MOVW(/MOVT) + ADD.W-register sequence instead of
7972    /// erroring. The small-imm fast path (imm <= 0xFFF) stays byte-identical.
7973    #[test]
7974    fn test_encode_add_imm_large_350() {
7975        let enc = ArmEncoder::new_thumb2();
7976
7977        // --- Fast path unchanged: imm <= 0xFFF is a single 4-byte ADD.W ---
7978        let small = enc
7979            .encode_thumb32_add_imm(&Reg::R0, &Reg::R1, 0x123)
7980            .unwrap();
7981        assert_eq!(small.len(), 4, "small imm must stay a single instruction");
7982
7983        // helper: decode a Thumb-2 MOVW/MOVT halfword pair back to its imm16
7984        fn movx_imm16(b: &[u8]) -> u32 {
7985            let hw1 = u16::from_le_bytes([b[0], b[1]]) as u32;
7986            let hw2 = u16::from_le_bytes([b[2], b[3]]) as u32;
7987            let imm4 = hw1 & 0xF;
7988            let i = (hw1 >> 10) & 1;
7989            let imm3 = (hw2 >> 12) & 0x7;
7990            let imm8 = hw2 & 0xFF;
7991            (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8
7992        }
7993        fn movx_rd(b: &[u8]) -> u32 {
7994            (u16::from_le_bytes([b[2], b[3]]) as u32 >> 8) & 0xF
7995        }
7996
7997        // --- rd != rn: scratch is rd. imm = 70000 = 0x11170 needs MOVW+MOVT. ---
7998        // 0x11170: lo16 = 0x1170, hi16 = 0x0001
7999        let seq = enc
8000            .encode_thumb32_add_imm(&Reg::R12, &Reg::R0, 70000)
8001            .unwrap();
8002        assert_eq!(seq.len(), 12, "MOVW + MOVT + ADD = 12 bytes");
8003        // MOVW r12, #0x1170
8004        assert_eq!(u16::from_le_bytes([seq[0], seq[1]]) & 0xFBF0, 0xF240);
8005        assert_eq!(movx_rd(&seq[0..4]), 12);
8006        assert_eq!(movx_imm16(&seq[0..4]), 0x1170);
8007        // MOVT r12, #0x0001
8008        assert_eq!(u16::from_le_bytes([seq[4], seq[5]]) & 0xFBF0, 0xF2C0);
8009        assert_eq!(movx_rd(&seq[4..8]), 12);
8010        assert_eq!(movx_imm16(&seq[4..8]), 0x0001);
8011        // ADD.W r12, r0, r12  (EB00 | rn=0 ; rd=12, rm=12)
8012        let add1 = u16::from_le_bytes([seq[8], seq[9]]) as u32;
8013        let add2 = u16::from_le_bytes([seq[10], seq[11]]) as u32;
8014        assert_eq!(add1 & 0xFFF0, 0xEB00);
8015        assert_eq!(add1 & 0xF, 0); // rn = r0
8016        assert_eq!((add2 >> 8) & 0xF, 12); // rd = r12
8017        assert_eq!(add2 & 0xF, 12); // rm = scratch = r12
8018        // The materialized scratch must reconstruct exactly 70000.
8019        assert_eq!(
8020            (movx_imm16(&seq[4..8]) << 16) | movx_imm16(&seq[0..4]),
8021            70000
8022        );
8023
8024        // --- imm <= 0xFFFF: MOVT is skipped (MOVW + ADD = 8 bytes). ---
8025        let seq16 = enc
8026            .encode_thumb32_add_imm(&Reg::R3, &Reg::R0, 0xABCD)
8027            .unwrap();
8028        assert_eq!(seq16.len(), 8, "imm <= 0xFFFF skips MOVT");
8029        assert_eq!(movx_imm16(&seq16[0..4]), 0xABCD);
8030        assert_eq!(movx_rd(&seq16[0..4]), 3); // scratch = rd = r3
8031
8032        // --- rd == rn (in-place add): scratch must be R12, not rd. ---
8033        // imm = 0x12345: lo16 = 0x2345, hi16 = 0x0001
8034        let inplace = enc
8035            .encode_thumb32_add_imm(&Reg::R5, &Reg::R5, 0x12345)
8036            .unwrap();
8037        assert_eq!(inplace.len(), 12);
8038        assert_eq!(movx_rd(&inplace[0..4]), 12, "rd==rn must use R12 scratch");
8039        assert_eq!(
8040            (movx_imm16(&inplace[4..8]) << 16) | movx_imm16(&inplace[0..4]),
8041            0x12345
8042        );
8043        // ADD.W r5, r5, r12 — rm must be the scratch (12), never rn.
8044        let ip_add2 = u16::from_le_bytes([inplace[10], inplace[11]]) as u32;
8045        assert_eq!(ip_add2 & 0xF, 12);
8046        assert_eq!((ip_add2 >> 8) & 0xF, 5);
8047    }
8048
8049    /// #350 follow-up — the `encoder_no_panic` fuzz harness drives the encoder
8050    /// with ARBITRARY registers, including the one case the in-place lowering
8051    /// cannot serve: rd==rn==R12. There the scratch (R12, the reserved encoder
8052    /// register) would alias Rn and clobber it before the ADD reads it. The
8053    /// encoder contract (#180/#185) is Ok-or-Err, never a panic — so this must
8054    /// return Err, not assert. (Real codegen never emits rd==rn==R12 because R12
8055    /// is non-allocatable; this guards only the fuzz/adversarial path.)
8056    #[test]
8057    fn test_encode_add_imm_large_rd_rn_r12_errs_not_panics_350() {
8058        let enc = ArmEncoder::new_thumb2();
8059        // Out-of-range imm with rd==rn==R12: no free scratch -> Err.
8060        let r = enc.encode_thumb32_add_imm(&Reg::R12, &Reg::R12, 70000);
8061        assert!(
8062            r.is_err(),
8063            "rd==rn==R12 with out-of-range imm must Err (no free scratch), got {r:?}"
8064        );
8065        // Small imm with rd==rn==R12 still takes the single-instruction fast path
8066        // (no scratch needed) and must succeed — the guard is scoped to the
8067        // out-of-range lowering only.
8068        let small = enc.encode_thumb32_add_imm(&Reg::R12, &Reg::R12, 0x10);
8069        assert!(small.is_ok(), "small imm needs no scratch, must stay Ok");
8070    }
8071
8072    /// #378 — `encode_operand2` (ARM32 data-processing operand) must FAIL
8073    /// HONESTLY on an immediate that is not a valid rotated immediate, rather
8074    /// than silently masking it to `imm & 0xFF` and emitting a WRONG
8075    /// instruction. `0x1FF` has 9 set bits, so it cannot come from rotating an
8076    /// 8-bit imm8 — non-encodable. Real codegen materializes large constants via
8077    /// MOVW/MOVT; this guards the encoder's Ok-or-Err contract (#180/#185)
8078    /// directly. It is an Err (not a panic) so the `encoder_no_panic` fuzz
8079    /// harness — which drives arbitrary operands — still passes.
8080    #[test]
8081    fn test_encode_operand2_non_rotatable_imm_errs_not_masks_378() {
8082        let enc = ArmEncoder::new_arm32();
8083        let bad = enc.encode(&ArmOp::Add {
8084            rd: Reg::R0,
8085            rn: Reg::R1,
8086            op2: Operand2::Imm(0x1FF),
8087        });
8088        assert!(
8089            bad.is_err(),
8090            "non-rotatable ARM32 immediate 0x1FF must Err (was silently masked \
8091             to 0xFF), got {bad:?}"
8092        );
8093        // A representable rotated immediate still encodes fine (regression guard).
8094        let ok = enc.encode(&ArmOp::Add {
8095            rd: Reg::R0,
8096            rn: Reg::R1,
8097            op2: Operand2::Imm(0xFF),
8098        });
8099        assert!(
8100            ok.is_ok(),
8101            "0xFF is a valid rotated immediate, must stay Ok"
8102        );
8103    }
8104
8105    #[test]
8106    fn test_encode_ldr_arm32() {
8107        let encoder = ArmEncoder::new_arm32();
8108        let op = ArmOp::Ldr {
8109            rd: Reg::R0,
8110            addr: MemAddr::imm(Reg::R1, 4),
8111        };
8112
8113        let code = encoder.encode(&op).unwrap();
8114        assert_eq!(code.len(), 4);
8115
8116        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8117        // Verify load bit is set
8118        assert_eq!(instr & 0x00100000, 0x00100000);
8119    }
8120
8121    #[test]
8122    fn test_encode_str_arm32() {
8123        let encoder = ArmEncoder::new_arm32();
8124        let op = ArmOp::Str {
8125            rd: Reg::R0,
8126            addr: MemAddr::imm(Reg::SP, 0),
8127        };
8128
8129        let code = encoder.encode(&op).unwrap();
8130        assert_eq!(code.len(), 4);
8131    }
8132
8133    #[test]
8134    fn test_encode_branch_arm32() {
8135        let encoder = ArmEncoder::new_arm32();
8136        let op = ArmOp::Bl {
8137            label: "main".to_string(),
8138        };
8139
8140        let code = encoder.encode(&op).unwrap();
8141        assert_eq!(code.len(), 4);
8142
8143        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8144        // Verify BL opcode
8145        assert_eq!(instr & 0x0F000000, 0x0B000000);
8146    }
8147
8148    /// Regression test for #167 + #174: the Thumb-2 BL relocatable placeholder
8149    /// must carry a -4 addend so an R_ARM_THM_CALL nets to exactly the symbol S.
8150    /// The correct encoding is what `gas` emits for `bl <extern>`: f7ff fffe
8151    /// (hw1=0xF7FF, hw2=0xFFFE), little-endian bytes FF F7 FE FF.
8152    ///   - 0xD000 (J1=J2=0) → ~+0x600000 garbage addend: `bl c0000c` / truncated
8153    ///     to fit (#167).
8154    ///   - 0xF800 (addend 0) → lands at S+4, one instruction past the callee
8155    ///     entry (#174).
8156    ///   - 0xFFFE (addend -4) → lands at S. Correct.
8157    #[test]
8158    fn test_encode_thumb_bl_placeholder_addend_167_174() {
8159        let encoder = ArmEncoder::new_thumb2();
8160        let op = ArmOp::Bl {
8161            label: "callee".to_string(),
8162        };
8163
8164        let code = encoder.encode(&op).unwrap();
8165        assert_eq!(code.len(), 4, "Thumb-2 BL is 32-bit");
8166
8167        let hw1 = u16::from_le_bytes([code[0], code[1]]);
8168        let hw2 = u16::from_le_bytes([code[2], code[3]]);
8169        assert_eq!(hw1, 0xF7FF, "BL first halfword (matches gas `bl <extern>`)");
8170        assert_eq!(
8171            hw2, 0xFFFE,
8172            "BL second halfword must be 0xFFFE (-4 addend → nets to S), not 0xF800 (→ S+4, #174) or 0xD000 (#167)"
8173        );
8174        assert_ne!(hw2, 0xF800, "0xF800 (addend 0) lands at S+4 (#174)");
8175        assert_ne!(hw2, 0xD000, "0xD000 bakes in a ~+0x600000 addend (#167)");
8176    }
8177
8178    #[test]
8179    fn test_encode_sequence() {
8180        let encoder = ArmEncoder::new_arm32();
8181        let ops = vec![
8182            ArmOp::Mov {
8183                rd: Reg::R0,
8184                op2: Operand2::Imm(42),
8185            },
8186            ArmOp::Mov {
8187                rd: Reg::R1,
8188                op2: Operand2::Imm(10),
8189            },
8190            ArmOp::Add {
8191                rd: Reg::R2,
8192                rn: Reg::R0,
8193                op2: Operand2::Reg(Reg::R1),
8194            },
8195        ];
8196
8197        let code = encoder.encode_sequence(&ops).unwrap();
8198        assert_eq!(code.len(), 12); // 3 instructions * 4 bytes
8199    }
8200
8201    #[test]
8202    fn test_reg_to_bits() {
8203        assert_eq!(reg_to_bits(&Reg::R0), 0);
8204        assert_eq!(reg_to_bits(&Reg::R7), 7);
8205        assert_eq!(reg_to_bits(&Reg::SP), 13);
8206        assert_eq!(reg_to_bits(&Reg::LR), 14);
8207        assert_eq!(reg_to_bits(&Reg::PC), 15);
8208    }
8209
8210    #[test]
8211    fn test_encode_bitwise_operations() {
8212        let encoder = ArmEncoder::new_arm32();
8213
8214        let and_op = ArmOp::And {
8215            rd: Reg::R0,
8216            rn: Reg::R1,
8217            op2: Operand2::Reg(Reg::R2),
8218        };
8219        let and_code = encoder.encode(&and_op).unwrap();
8220        assert_eq!(and_code.len(), 4);
8221
8222        let orr_op = ArmOp::Orr {
8223            rd: Reg::R0,
8224            rn: Reg::R1,
8225            op2: Operand2::Reg(Reg::R2),
8226        };
8227        let orr_code = encoder.encode(&orr_op).unwrap();
8228        assert_eq!(orr_code.len(), 4);
8229
8230        let eor_op = ArmOp::Eor {
8231            rd: Reg::R0,
8232            rn: Reg::R1,
8233            op2: Operand2::Reg(Reg::R2),
8234        };
8235        let eor_code = encoder.encode(&eor_op).unwrap();
8236        assert_eq!(eor_code.len(), 4);
8237    }
8238
8239    // === Thumb-2 32-bit encoding tests ===
8240
8241    #[test]
8242    fn test_encode_sdiv_thumb2() {
8243        let encoder = ArmEncoder::new_thumb2();
8244        let op = ArmOp::Sdiv {
8245            rd: Reg::R0,
8246            rn: Reg::R1,
8247            rm: Reg::R2,
8248        };
8249
8250        let code = encoder.encode(&op).unwrap();
8251        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
8252
8253        // SDIV R0, R1, R2: 0xFB91 0xF0F2
8254        // First halfword: 0xFB90 | Rn(1) = 0xFB91
8255        // Second halfword: 0xF0F0 | Rd(0)<<8 | Rm(2) = 0xF0F2
8256        // Little-endian: [0x91, 0xFB, 0xF2, 0xF0]
8257        assert_eq!(code[0], 0x91);
8258        assert_eq!(code[1], 0xFB);
8259        assert_eq!(code[2], 0xF2);
8260        assert_eq!(code[3], 0xF0);
8261    }
8262
8263    #[test]
8264    fn test_encode_udiv_thumb2() {
8265        let encoder = ArmEncoder::new_thumb2();
8266        let op = ArmOp::Udiv {
8267            rd: Reg::R0,
8268            rn: Reg::R1,
8269            rm: Reg::R2,
8270        };
8271
8272        let code = encoder.encode(&op).unwrap();
8273        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
8274
8275        // UDIV R0, R1, R2: 0xFBB1 0xF0F2
8276        // Little-endian: [0xB1, 0xFB, 0xF2, 0xF0]
8277        assert_eq!(code[0], 0xB1);
8278        assert_eq!(code[1], 0xFB);
8279        assert_eq!(code[2], 0xF2);
8280        assert_eq!(code[3], 0xF0);
8281    }
8282
8283    #[test]
8284    fn test_encode_mul_thumb2() {
8285        let encoder = ArmEncoder::new_thumb2();
8286        let op = ArmOp::Mul {
8287            rd: Reg::R0,
8288            rn: Reg::R1,
8289            rm: Reg::R2,
8290        };
8291
8292        let code = encoder.encode(&op).unwrap();
8293        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
8294    }
8295
8296    #[test]
8297    fn test_encode_and_thumb2() {
8298        let encoder = ArmEncoder::new_thumb2();
8299        let op = ArmOp::And {
8300            rd: Reg::R0,
8301            rn: Reg::R1,
8302            op2: Operand2::Reg(Reg::R2),
8303        };
8304
8305        let code = encoder.encode(&op).unwrap();
8306        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
8307    }
8308
8309    #[test]
8310    fn test_encode_lsl_thumb2_low_regs() {
8311        let encoder = ArmEncoder::new_thumb2();
8312        let op = ArmOp::Lsl {
8313            rd: Reg::R0,
8314            rn: Reg::R1,
8315            shift: 5,
8316        };
8317
8318        let code = encoder.encode(&op).unwrap();
8319        assert_eq!(code.len(), 2); // 16-bit for low registers
8320    }
8321
8322    #[test]
8323    fn test_encode_clz_thumb2() {
8324        let encoder = ArmEncoder::new_thumb2();
8325        let op = ArmOp::Clz {
8326            rd: Reg::R0,
8327            rm: Reg::R1,
8328        };
8329
8330        let code = encoder.encode(&op).unwrap();
8331        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
8332    }
8333
8334    #[test]
8335    fn test_encode_bx_thumb2() {
8336        let encoder = ArmEncoder::new_thumb2();
8337        let op = ArmOp::Bx { rm: Reg::LR };
8338
8339        let code = encoder.encode(&op).unwrap();
8340        assert_eq!(code.len(), 2); // 16-bit instruction
8341
8342        // BX LR: 0x4770
8343        assert_eq!(code, vec![0x70, 0x47]);
8344    }
8345
8346    // ========================================================================
8347    // f32 pseudo-op encoding tests
8348    // ========================================================================
8349
8350    #[test]
8351    fn test_encode_f32_abs_arm32() {
8352        let encoder = ArmEncoder::new_arm32();
8353        let op = ArmOp::F32Abs {
8354            sd: VfpReg::S0,
8355            sm: VfpReg::S2,
8356        };
8357        let code = encoder.encode(&op).unwrap();
8358        assert_eq!(code.len(), 4); // Single VFP instruction
8359    }
8360
8361    #[test]
8362    fn test_encode_f32_neg_arm32() {
8363        let encoder = ArmEncoder::new_arm32();
8364        let op = ArmOp::F32Neg {
8365            sd: VfpReg::S0,
8366            sm: VfpReg::S2,
8367        };
8368        let code = encoder.encode(&op).unwrap();
8369        assert_eq!(code.len(), 4);
8370    }
8371
8372    #[test]
8373    fn test_encode_f32_sqrt_arm32() {
8374        let encoder = ArmEncoder::new_arm32();
8375        let op = ArmOp::F32Sqrt {
8376            sd: VfpReg::S0,
8377            sm: VfpReg::S2,
8378        };
8379        let code = encoder.encode(&op).unwrap();
8380        assert_eq!(code.len(), 4);
8381    }
8382
8383    #[test]
8384    fn test_encode_f32_ceil_arm32() {
8385        let encoder = ArmEncoder::new_arm32();
8386        let op = ArmOp::F32Ceil {
8387            sd: VfpReg::S0,
8388            sm: VfpReg::S2,
8389        };
8390        let code = encoder.encode(&op).unwrap();
8391        // VMRS + BIC + ORR + VMSR + VCVT.S32.F32 + VMRS + BIC + VMSR + VCVT.F32.S32
8392        assert_eq!(code.len(), 36);
8393    }
8394
8395    #[test]
8396    fn test_encode_f32_floor_thumb2() {
8397        let encoder = ArmEncoder::new_thumb2();
8398        let op = ArmOp::F32Floor {
8399            sd: VfpReg::S0,
8400            sm: VfpReg::S2,
8401        };
8402        let code = encoder.encode(&op).unwrap();
8403        // VMRS + BIC.W + ORR.W + VMSR + VCVT + VMRS + BIC.W + VMSR + VCVT.F32.S32
8404        assert_eq!(code.len(), 36);
8405    }
8406
8407    #[test]
8408    fn test_encode_f32_min_arm32() {
8409        let encoder = ArmEncoder::new_arm32();
8410        let op = ArmOp::F32Min {
8411            sd: VfpReg::S0,
8412            sn: VfpReg::S2,
8413            sm: VfpReg::S4,
8414        };
8415        let code = encoder.encode(&op).unwrap();
8416        assert_eq!(code.len(), 16); // VMOV + VCMP + VMRS + conditional VMOV
8417    }
8418
8419    #[test]
8420    fn test_encode_f32_max_thumb2() {
8421        let encoder = ArmEncoder::new_thumb2();
8422        let op = ArmOp::F32Max {
8423            sd: VfpReg::S0,
8424            sn: VfpReg::S2,
8425            sm: VfpReg::S4,
8426        };
8427        let code = encoder.encode(&op).unwrap();
8428        // VMOV(4) + VCMP(4) + VMRS(4) + IT(2) + VMOV(4) = 18
8429        assert_eq!(code.len(), 18);
8430    }
8431
8432    #[test]
8433    fn test_encode_f32_copysign_arm32() {
8434        let encoder = ArmEncoder::new_arm32();
8435        let op = ArmOp::F32Copysign {
8436            sd: VfpReg::S0,
8437            sn: VfpReg::S2,
8438            sm: VfpReg::S4,
8439        };
8440        let code = encoder.encode(&op).unwrap();
8441        // VMOV + VMOV + AND + BIC + ORR + VMOV = 6 * 4 = 24
8442        assert_eq!(code.len(), 24);
8443    }
8444
8445    // ========================================================================
8446    // f64 encoding tests
8447    // ========================================================================
8448
8449    #[test]
8450    fn test_encode_f64_add_arm32() {
8451        let encoder = ArmEncoder::new_arm32();
8452        let op = ArmOp::F64Add {
8453            dd: VfpReg::D0,
8454            dn: VfpReg::D1,
8455            dm: VfpReg::D2,
8456        };
8457        let code = encoder.encode(&op).unwrap();
8458        assert_eq!(code.len(), 4);
8459        // VADD.F64 D0, D1, D2: check coprocessor is cp11 (0xB)
8460        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8461        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11
8462    }
8463
8464    #[test]
8465    fn test_encode_f64_sub_thumb2() {
8466        let encoder = ArmEncoder::new_thumb2();
8467        let op = ArmOp::F64Sub {
8468            dd: VfpReg::D0,
8469            dn: VfpReg::D1,
8470            dm: VfpReg::D2,
8471        };
8472        let code = encoder.encode(&op).unwrap();
8473        assert_eq!(code.len(), 4); // 32-bit VFP as two Thumb halfwords
8474    }
8475
8476    #[test]
8477    fn test_encode_f64_mul_arm32() {
8478        let encoder = ArmEncoder::new_arm32();
8479        let op = ArmOp::F64Mul {
8480            dd: VfpReg::D0,
8481            dn: VfpReg::D1,
8482            dm: VfpReg::D2,
8483        };
8484        let code = encoder.encode(&op).unwrap();
8485        assert_eq!(code.len(), 4);
8486    }
8487
8488    #[test]
8489    fn test_encode_f64_div_arm32() {
8490        let encoder = ArmEncoder::new_arm32();
8491        let op = ArmOp::F64Div {
8492            dd: VfpReg::D0,
8493            dn: VfpReg::D1,
8494            dm: VfpReg::D2,
8495        };
8496        let code = encoder.encode(&op).unwrap();
8497        assert_eq!(code.len(), 4);
8498    }
8499
8500    #[test]
8501    fn test_encode_f64_abs_arm32() {
8502        let encoder = ArmEncoder::new_arm32();
8503        let op = ArmOp::F64Abs {
8504            dd: VfpReg::D0,
8505            dm: VfpReg::D2,
8506        };
8507        let code = encoder.encode(&op).unwrap();
8508        assert_eq!(code.len(), 4);
8509    }
8510
8511    #[test]
8512    fn test_encode_f64_neg_arm32() {
8513        let encoder = ArmEncoder::new_arm32();
8514        let op = ArmOp::F64Neg {
8515            dd: VfpReg::D0,
8516            dm: VfpReg::D2,
8517        };
8518        let code = encoder.encode(&op).unwrap();
8519        assert_eq!(code.len(), 4);
8520    }
8521
8522    #[test]
8523    fn test_encode_f64_sqrt_arm32() {
8524        let encoder = ArmEncoder::new_arm32();
8525        let op = ArmOp::F64Sqrt {
8526            dd: VfpReg::D0,
8527            dm: VfpReg::D2,
8528        };
8529        let code = encoder.encode(&op).unwrap();
8530        assert_eq!(code.len(), 4);
8531    }
8532
8533    #[test]
8534    fn test_encode_f64_load_arm32() {
8535        let encoder = ArmEncoder::new_arm32();
8536        let op = ArmOp::F64Load {
8537            dd: VfpReg::D0,
8538            addr: MemAddr::imm(Reg::R0, 8),
8539        };
8540        let code = encoder.encode(&op).unwrap();
8541        assert_eq!(code.len(), 4);
8542        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8543        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11 for F64
8544        assert_eq!(instr & 0xFF, 2); // offset 8 / 4 = 2
8545    }
8546
8547    #[test]
8548    fn test_encode_f64_store_thumb2() {
8549        let encoder = ArmEncoder::new_thumb2();
8550        let op = ArmOp::F64Store {
8551            dd: VfpReg::D0,
8552            addr: MemAddr::imm(Reg::SP, 0),
8553        };
8554        let code = encoder.encode(&op).unwrap();
8555        assert_eq!(code.len(), 4);
8556    }
8557
8558    #[test]
8559    fn test_encode_f64_compare_arm32() {
8560        let encoder = ArmEncoder::new_arm32();
8561        let op = ArmOp::F64Eq {
8562            rd: Reg::R0,
8563            dn: VfpReg::D0,
8564            dm: VfpReg::D1,
8565        };
8566        let code = encoder.encode(&op).unwrap();
8567        assert_eq!(code.len(), 16); // VCMP + VMRS + MOV #0 + MOVcond #1
8568    }
8569
8570    #[test]
8571    fn test_encode_f64_compare_thumb2() {
8572        let encoder = ArmEncoder::new_thumb2();
8573        let op = ArmOp::F64Lt {
8574            rd: Reg::R0,
8575            dn: VfpReg::D0,
8576            dm: VfpReg::D1,
8577        };
8578        let code = encoder.encode(&op).unwrap();
8579        // VCMP(4) + VMRS(4) + MOVS(2) + IT(2) + MOV(2) = 14
8580        assert_eq!(code.len(), 14);
8581    }
8582
8583    #[test]
8584    fn test_encode_f64_const_arm32() {
8585        let encoder = ArmEncoder::new_arm32();
8586        let op = ArmOp::F64Const {
8587            dd: VfpReg::D0,
8588            value: 3.125,
8589        };
8590        let code = encoder.encode(&op).unwrap();
8591        // MOVW(4) + MOVT(4) + MOVW(4) + MOVT(4) + VMOV(4) = 20
8592        assert_eq!(code.len(), 20);
8593    }
8594
8595    #[test]
8596    fn test_encode_f64_const_thumb2() {
8597        let encoder = ArmEncoder::new_thumb2();
8598        let op = ArmOp::F64Const {
8599            dd: VfpReg::D0,
8600            value: 2.5,
8601        };
8602        let code = encoder.encode(&op).unwrap();
8603        // MOVW(4) + MOVT(4) + MOVW(4) + MOVT(4) + VMOV(4) = 20
8604        assert_eq!(code.len(), 20);
8605    }
8606
8607    #[test]
8608    fn test_encode_f64_convert_i32s_arm32() {
8609        let encoder = ArmEncoder::new_arm32();
8610        let op = ArmOp::F64ConvertI32S {
8611            dd: VfpReg::D0,
8612            rm: Reg::R0,
8613        };
8614        let code = encoder.encode(&op).unwrap();
8615        // VMOV(4) + VCVT(4) = 8
8616        assert_eq!(code.len(), 8);
8617    }
8618
8619    #[test]
8620    fn test_encode_f64_promote_f32_arm32() {
8621        let encoder = ArmEncoder::new_arm32();
8622        let op = ArmOp::F64PromoteF32 {
8623            dd: VfpReg::D0,
8624            sm: VfpReg::S0,
8625        };
8626        let code = encoder.encode(&op).unwrap();
8627        assert_eq!(code.len(), 4); // Single VCVT.F64.F32 instruction
8628    }
8629
8630    #[test]
8631    fn test_encode_f64_promote_f32_thumb2() {
8632        let encoder = ArmEncoder::new_thumb2();
8633        let op = ArmOp::F64PromoteF32 {
8634            dd: VfpReg::D0,
8635            sm: VfpReg::S0,
8636        };
8637        let code = encoder.encode(&op).unwrap();
8638        assert_eq!(code.len(), 4);
8639    }
8640
8641    #[test]
8642    fn test_encode_i32_trunc_f64s_arm32() {
8643        let encoder = ArmEncoder::new_arm32();
8644        let op = ArmOp::I32TruncF64S {
8645            rd: Reg::R0,
8646            dm: VfpReg::D0,
8647        };
8648        let code = encoder.encode(&op).unwrap();
8649        // VCVT(4) + VMOV(4) = 8
8650        assert_eq!(code.len(), 8);
8651    }
8652
8653    #[test]
8654    fn test_encode_f64_reinterpret_i64_arm32() {
8655        let encoder = ArmEncoder::new_arm32();
8656        let op = ArmOp::F64ReinterpretI64 {
8657            dd: VfpReg::D0,
8658            rmlo: Reg::R0,
8659            rmhi: Reg::R1,
8660        };
8661        let code = encoder.encode(&op).unwrap();
8662        assert_eq!(code.len(), 4); // Single VMOV instruction
8663    }
8664
8665    #[test]
8666    fn test_encode_i64_reinterpret_f64_thumb2() {
8667        let encoder = ArmEncoder::new_thumb2();
8668        let op = ArmOp::I64ReinterpretF64 {
8669            rdlo: Reg::R0,
8670            rdhi: Reg::R1,
8671            dm: VfpReg::D0,
8672        };
8673        let code = encoder.encode(&op).unwrap();
8674        assert_eq!(code.len(), 4);
8675    }
8676
8677    #[test]
8678    fn test_encode_f64_trunc_thumb2() {
8679        let encoder = ArmEncoder::new_thumb2();
8680        let op = ArmOp::F64Trunc {
8681            dd: VfpReg::D0,
8682            dm: VfpReg::D1,
8683        };
8684        let code = encoder.encode(&op).unwrap();
8685        // Two VFP instructions via Thumb encoding
8686        assert_eq!(code.len(), 8);
8687    }
8688
8689    #[test]
8690    fn test_encode_f64_min_arm32() {
8691        let encoder = ArmEncoder::new_arm32();
8692        let op = ArmOp::F64Min {
8693            dd: VfpReg::D0,
8694            dn: VfpReg::D1,
8695            dm: VfpReg::D2,
8696        };
8697        let code = encoder.encode(&op).unwrap();
8698        // VMOV + VCMP + VMRS + conditional VMOV = 16
8699        assert_eq!(code.len(), 16);
8700    }
8701
8702    #[test]
8703    fn test_f64_cp11_encoding() {
8704        // Verify that F64 instructions use coprocessor 11 (0xB), not 10 (0xA)
8705        let encoder = ArmEncoder::new_arm32();
8706
8707        // F64Add
8708        let code = encoder
8709            .encode(&ArmOp::F64Add {
8710                dd: VfpReg::D0,
8711                dn: VfpReg::D0,
8712                dm: VfpReg::D0,
8713            })
8714            .unwrap();
8715        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8716        assert_eq!((instr >> 8) & 0xF, 0xB, "F64 should use cp11");
8717
8718        // F32Add for comparison
8719        let code = encoder
8720            .encode(&ArmOp::F32Add {
8721                sd: VfpReg::S0,
8722                sn: VfpReg::S0,
8723                sm: VfpReg::S0,
8724            })
8725            .unwrap();
8726        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8727        assert_eq!((instr >> 8) & 0xF, 0xA, "F32 should use cp10");
8728    }
8729
8730    #[test]
8731    fn test_dreg_encoding_higher_registers() {
8732        let encoder = ArmEncoder::new_arm32();
8733
8734        // Test with D15 (highest register)
8735        let op = ArmOp::F64Add {
8736            dd: VfpReg::D15,
8737            dn: VfpReg::D14,
8738            dm: VfpReg::D13,
8739        };
8740        let code = encoder.encode(&op).unwrap();
8741        assert_eq!(code.len(), 4);
8742
8743        // Verify the register encoding worked (instruction is valid)
8744        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8745        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11
8746    }
8747
8748    // ========================================================================
8749    // Control flow encoding tests
8750    // ========================================================================
8751
8752    #[test]
8753    fn test_encode_label_emits_no_bytes() {
8754        let encoder = ArmEncoder::new_thumb2();
8755        let op = ArmOp::Label {
8756            name: ".Lblock_end_0".to_string(),
8757        };
8758        let code = encoder.encode(&op).unwrap();
8759        assert!(code.is_empty(), "Label should emit zero bytes");
8760
8761        let encoder32 = ArmEncoder::new_arm32();
8762        let code32 = encoder32.encode(&op).unwrap();
8763        assert!(
8764            code32.is_empty(),
8765            "Label should emit zero bytes in ARM32 too"
8766        );
8767    }
8768
8769    #[test]
8770    fn test_encode_bcc_eq_thumb2() {
8771        use synth_synthesis::Condition;
8772        let encoder = ArmEncoder::new_thumb2();
8773        let op = ArmOp::Bcc {
8774            cond: Condition::EQ,
8775            label: "target".to_string(),
8776        };
8777        let code = encoder.encode(&op).unwrap();
8778        assert_eq!(code.len(), 2); // 16-bit conditional branch
8779
8780        // BEQ with offset 0: 0xD000 in little-endian
8781        assert_eq!(code, vec![0x00, 0xD0]);
8782    }
8783
8784    #[test]
8785    fn test_encode_bcc_ne_thumb2() {
8786        use synth_synthesis::Condition;
8787        let encoder = ArmEncoder::new_thumb2();
8788        let op = ArmOp::Bcc {
8789            cond: Condition::NE,
8790            label: "target".to_string(),
8791        };
8792        let code = encoder.encode(&op).unwrap();
8793        assert_eq!(code.len(), 2);
8794
8795        // BNE with offset 0: 0xD100 in little-endian
8796        assert_eq!(code, vec![0x00, 0xD1]);
8797    }
8798
8799    #[test]
8800    fn test_encode_bcc_arm32() {
8801        use synth_synthesis::Condition;
8802        let encoder = ArmEncoder::new_arm32();
8803        let op = ArmOp::Bcc {
8804            cond: Condition::EQ,
8805            label: "target".to_string(),
8806        };
8807        let code = encoder.encode(&op).unwrap();
8808        assert_eq!(code.len(), 4); // 32-bit ARM instruction
8809
8810        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8811        // BEQ: cond=0x0, opcode=0xA, offset=0
8812        assert_eq!(instr & 0xF0000000, 0x00000000); // EQ condition
8813        assert_eq!(instr & 0x0F000000, 0x0A000000); // Branch opcode
8814    }
8815
8816    #[test]
8817    fn test_encode_udf_thumb2() {
8818        let encoder = ArmEncoder::new_thumb2();
8819        let op = ArmOp::Udf { imm: 0 };
8820        let code = encoder.encode(&op).unwrap();
8821        assert_eq!(code.len(), 2); // 16-bit
8822
8823        // UDF #0: 0xDE00 in little-endian
8824        assert_eq!(code, vec![0x00, 0xDE]);
8825    }
8826
8827    #[test]
8828    fn test_encode_nop_thumb2() {
8829        let encoder = ArmEncoder::new_thumb2();
8830        let op = ArmOp::Nop;
8831        let code = encoder.encode(&op).unwrap();
8832        assert_eq!(code.len(), 2); // 16-bit
8833
8834        // NOP: 0xBF00 in little-endian
8835        assert_eq!(code, vec![0x00, 0xBF]);
8836    }
8837
8838    // =========================================================================
8839    // i64 Thumb-2 encoding tests
8840    // =========================================================================
8841
8842    #[test]
8843    fn test_encode_i64_add_thumb2() {
8844        let encoder = ArmEncoder::new_thumb2();
8845        let op = ArmOp::I64Add {
8846            rdlo: Reg::R0,
8847            rdhi: Reg::R1,
8848            rnlo: Reg::R0,
8849            rnhi: Reg::R1,
8850            rmlo: Reg::R2,
8851            rmhi: Reg::R3,
8852        };
8853        let code = encoder.encode(&op).unwrap();
8854        // Should emit ADDS (2 bytes) + ADC.W (4 bytes) = 6 bytes
8855        assert_eq!(code.len(), 6, "I64Add should be 6 bytes (ADDS + ADC.W)");
8856    }
8857
8858    #[test]
8859    fn test_encode_i64_sub_thumb2() {
8860        let encoder = ArmEncoder::new_thumb2();
8861        let op = ArmOp::I64Sub {
8862            rdlo: Reg::R0,
8863            rdhi: Reg::R1,
8864            rnlo: Reg::R0,
8865            rnhi: Reg::R1,
8866            rmlo: Reg::R2,
8867            rmhi: Reg::R3,
8868        };
8869        let code = encoder.encode(&op).unwrap();
8870        // Should emit SUBS (2 bytes) + SBC.W (4 bytes) = 6 bytes
8871        assert_eq!(code.len(), 6, "I64Sub should be 6 bytes (SUBS + SBC.W)");
8872    }
8873
8874    #[test]
8875    fn test_encode_i64_and_thumb2() {
8876        let encoder = ArmEncoder::new_thumb2();
8877        let op = ArmOp::I64And {
8878            rdlo: Reg::R0,
8879            rdhi: Reg::R1,
8880            rnlo: Reg::R0,
8881            rnhi: Reg::R1,
8882            rmlo: Reg::R2,
8883            rmhi: Reg::R3,
8884        };
8885        let code = encoder.encode(&op).unwrap();
8886        // AND.W (4 bytes) + AND.W (4 bytes) = 8 bytes
8887        assert!(code.len() >= 4, "I64And should emit at least 4 bytes");
8888    }
8889
8890    #[test]
8891    fn test_encode_i64_or_thumb2() {
8892        let encoder = ArmEncoder::new_thumb2();
8893        let op = ArmOp::I64Or {
8894            rdlo: Reg::R0,
8895            rdhi: Reg::R1,
8896            rnlo: Reg::R0,
8897            rnhi: Reg::R1,
8898            rmlo: Reg::R2,
8899            rmhi: Reg::R3,
8900        };
8901        let code = encoder.encode(&op).unwrap();
8902        assert!(code.len() >= 4, "I64Or should emit at least 4 bytes");
8903    }
8904
8905    #[test]
8906    fn test_encode_i64_xor_thumb2() {
8907        let encoder = ArmEncoder::new_thumb2();
8908        let op = ArmOp::I64Xor {
8909            rdlo: Reg::R0,
8910            rdhi: Reg::R1,
8911            rnlo: Reg::R0,
8912            rnhi: Reg::R1,
8913            rmlo: Reg::R2,
8914            rmhi: Reg::R3,
8915        };
8916        let code = encoder.encode(&op).unwrap();
8917        assert!(code.len() >= 4, "I64Xor should emit at least 4 bytes");
8918    }
8919
8920    #[test]
8921    fn test_encode_i64_const_small_thumb2() {
8922        let encoder = ArmEncoder::new_thumb2();
8923        // Small constant: only needs MOVW for each half
8924        let op = ArmOp::I64Const {
8925            rdlo: Reg::R0,
8926            rdhi: Reg::R1,
8927            value: 42,
8928        };
8929        let code = encoder.encode(&op).unwrap();
8930        // MOVW R0, #42 (4 bytes) + MOVW R1, #0 (4 bytes) = 8 bytes minimum
8931        assert!(code.len() >= 8, "I64Const should emit at least 8 bytes");
8932    }
8933
8934    #[test]
8935    fn test_encode_i64_const_large_thumb2() {
8936        let encoder = ArmEncoder::new_thumb2();
8937        // Large constant: needs MOVW+MOVT for each half
8938        let op = ArmOp::I64Const {
8939            rdlo: Reg::R0,
8940            rdhi: Reg::R1,
8941            value: 0x1234_5678_9ABC_DEF0_u64 as i64,
8942        };
8943        let code = encoder.encode(&op).unwrap();
8944        // MOVW + MOVT for lo (8 bytes) + MOVW + MOVT for hi (8 bytes) = 16 bytes
8945        assert_eq!(
8946            code.len(),
8947            16,
8948            "I64Const with large value should be 16 bytes"
8949        );
8950    }
8951
8952    #[test]
8953    fn test_encode_i64_extend_i32_s_thumb2() {
8954        let encoder = ArmEncoder::new_thumb2();
8955        let op = ArmOp::I64ExtendI32S {
8956            rdlo: Reg::R0,
8957            rdhi: Reg::R1,
8958            rn: Reg::R0,
8959        };
8960        let code = encoder.encode(&op).unwrap();
8961        // When rdlo == rn, only ASR (4 bytes) is emitted
8962        assert_eq!(
8963            code.len(),
8964            4,
8965            "I64ExtendI32S (same reg) should be 4 bytes (ASR only)"
8966        );
8967    }
8968
8969    #[test]
8970    fn test_encode_i64_extend_i32_s_diff_reg_thumb2() {
8971        let encoder = ArmEncoder::new_thumb2();
8972        let op = ArmOp::I64ExtendI32S {
8973            rdlo: Reg::R0,
8974            rdhi: Reg::R1,
8975            rn: Reg::R2,
8976        };
8977        let code = encoder.encode(&op).unwrap();
8978        // MOV rdlo, rn (2 bytes for low regs) + ASR rdhi, rdlo, #31 (4 bytes) = 6 bytes
8979        assert!(
8980            code.len() >= 6,
8981            "I64ExtendI32S (diff reg) should be at least 6 bytes"
8982        );
8983    }
8984
8985    #[test]
8986    fn test_encode_i64_extend_i32_u_thumb2() {
8987        let encoder = ArmEncoder::new_thumb2();
8988        let op = ArmOp::I64ExtendI32U {
8989            rdlo: Reg::R0,
8990            rdhi: Reg::R1,
8991            rn: Reg::R0,
8992        };
8993        let code = encoder.encode(&op).unwrap();
8994        // When rdlo == rn, only MOV rdhi, #0 (2 bytes) is emitted
8995        assert_eq!(
8996            code.len(),
8997            2,
8998            "I64ExtendI32U (same reg) should be 2 bytes (MOV #0 only)"
8999        );
9000    }
9001
9002    #[test]
9003    fn test_encode_i32_wrap_i64_nop_thumb2() {
9004        let encoder = ArmEncoder::new_thumb2();
9005        // When rd == rnlo, should be a NOP
9006        let op = ArmOp::I32WrapI64 {
9007            rd: Reg::R0,
9008            rnlo: Reg::R0,
9009        };
9010        let code = encoder.encode(&op).unwrap();
9011        assert_eq!(code.len(), 2, "I32WrapI64 same reg should be NOP (2 bytes)");
9012        assert_eq!(code, vec![0x00, 0xBF]); // NOP
9013    }
9014
9015    #[test]
9016    fn test_encode_i32_wrap_i64_diff_reg_thumb2() {
9017        let encoder = ArmEncoder::new_thumb2();
9018        let op = ArmOp::I32WrapI64 {
9019            rd: Reg::R2,
9020            rnlo: Reg::R0,
9021        };
9022        let code = encoder.encode(&op).unwrap();
9023        // MOV R2, R0 (2 or 4 bytes)
9024        assert!(
9025            code.len() >= 2,
9026            "I32WrapI64 diff reg should emit at least 2 bytes"
9027        );
9028    }
9029
9030    #[test]
9031    fn test_encode_i64_eqz_thumb2() {
9032        let encoder = ArmEncoder::new_thumb2();
9033        let op = ArmOp::I64Eqz {
9034            rd: Reg::R0,
9035            rnlo: Reg::R0,
9036            rnhi: Reg::R1,
9037        };
9038        let code = encoder.encode(&op).unwrap();
9039        // Delegates to I64SetCondZ which is already encoded
9040        assert!(
9041            code.len() >= 6,
9042            "I64Eqz should emit at least 6 bytes for ORR+ITE+MOV+MOV"
9043        );
9044    }
9045
9046    #[test]
9047    fn test_encode_i64_eq_thumb2() {
9048        let encoder = ArmEncoder::new_thumb2();
9049        let op = ArmOp::I64Eq {
9050            rd: Reg::R0,
9051            rnlo: Reg::R0,
9052            rnhi: Reg::R1,
9053            rmlo: Reg::R2,
9054            rmhi: Reg::R3,
9055        };
9056        let code = encoder.encode(&op).unwrap();
9057        // Delegates to I64SetCond EQ: CMP lo + IT EQ + CMPEQ hi + ITE EQ + MOV 1 + MOV 0
9058        assert!(code.len() >= 10, "I64Eq should emit at least 10 bytes");
9059    }
9060
9061    #[test]
9062    fn test_encode_i64_ldr_thumb2() {
9063        let encoder = ArmEncoder::new_thumb2();
9064        let op = ArmOp::I64Ldr {
9065            rdlo: Reg::R0,
9066            rdhi: Reg::R1,
9067            addr: MemAddr::imm(Reg::SP, 0),
9068        };
9069        let code = encoder.encode(&op).unwrap();
9070        // Two LDR instructions (lo at offset, hi at offset+4)
9071        assert!(code.len() >= 4, "I64Ldr should emit at least 4 bytes");
9072    }
9073
9074    #[test]
9075    fn test_372_i64_ldr_indexed_materializes_address() {
9076        // #372: a memory i64.load carries an index register (R11 + addr + off).
9077        // The encoder must materialize `ip = base + index` (ADD.W) and load via
9078        // `[ip,#off]` — NOT drop the index. A frame (non-indexed) i64.load must
9079        // stay byte-identical (plain `[base,#off]`, no ADD).
9080        let encoder = ArmEncoder::new_thumb2();
9081        let indexed = encoder
9082            .encode(&ArmOp::I64Ldr {
9083                rdlo: Reg::R0,
9084                rdhi: Reg::R1,
9085                addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 0),
9086            })
9087            .unwrap();
9088        // ADD.W ip, fp, r0 = eb0b 0c00 (byte-verified vs arm-none-eabi-as).
9089        assert_eq!(
9090            &indexed[0..4],
9091            &[0x0b, 0xeb, 0x00, 0x0c],
9092            "indexed I64Ldr must start with ADD.W ip, base, index"
9093        );
9094        let frame = encoder
9095            .encode(&ArmOp::I64Ldr {
9096                rdlo: Reg::R0,
9097                rdhi: Reg::R1,
9098                addr: MemAddr::imm(Reg::SP, 8),
9099            })
9100            .unwrap();
9101        // No index -> no ADD.W prefix (byte-identical frame access).
9102        assert_ne!(
9103            &frame[0..2],
9104            &[0x0b, 0xeb],
9105            "frame (non-indexed) I64Ldr must NOT emit an ADD.W"
9106        );
9107    }
9108
9109    #[test]
9110    fn test_382_i64_ldst_large_offset_materializes_not_skips() {
9111        // #382: an indexed i64.load/store whose static offset > 0xFFF must
9112        // MATERIALIZE the offset into the base — NOT return Err (skip the fn).
9113        // Sequence for reg_imm(R11, R0, 5000): MOVW ip,#5000 ; ADD ip,r0,ip ;
9114        // ADD ip,ip,fp ; LDR/STR halves at [ip,#0] / [ip,#4]. Byte-verified tail
9115        // vs arm-none-eabi-as.
9116        let encoder = ArmEncoder::new_thumb2();
9117        // 0x1388 > 0xFFF (MemAddr is not Copy, so build it per use).
9118
9119        let ld = encoder
9120            .encode(&ArmOp::I64Ldr {
9121                rdlo: Reg::R0,
9122                rdhi: Reg::R1,
9123                addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 5000),
9124            })
9125            .expect("large-offset i64.load must lower, not skip");
9126        // MOVW ip,#0x1388 (4) + ADD ip,r0,ip (4) + ADD ip,ip,fp (4) + 2 LDR (8).
9127        assert_eq!(ld.len(), 20, "expected MOVW + 2×ADD + 2×LDR");
9128        // Must NOT be the small-offset `ADD.W ip, fp, r0` (0x0b 0xeb) prefix —
9129        // that path can only reach imm12 offsets.
9130        assert_ne!(
9131            &ld[0..2],
9132            &[0x0b, 0xeb],
9133            "must materialize the large offset"
9134        );
9135        // Effective base built in ip, then halves at [ip,#0] / [ip,#4].
9136        assert_eq!(
9137            &ld[4..20],
9138            &[
9139                0x00, 0xeb, 0x0c, 0x0c, // ADD.W ip, r0, ip
9140                0x0c, 0xeb, 0x0b, 0x0c, // ADD.W ip, ip, fp
9141                0xdc, 0xf8, 0x00, 0x00, // LDR.W r0, [ip, #0]
9142                0xdc, 0xf8, 0x04, 0x10, // LDR.W r1, [ip, #4]
9143            ],
9144            "large-offset i64.load must fold offset into ip and access [ip,#0]/[ip,#4]"
9145        );
9146
9147        // Store: same base materialization, STR halves.
9148        let st = encoder
9149            .encode(&ArmOp::I64Str {
9150                rdlo: Reg::R2,
9151                rdhi: Reg::R3,
9152                addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 5000),
9153            })
9154            .expect("large-offset i64.store must lower, not skip");
9155        assert_eq!(st.len(), 20);
9156        assert_eq!(
9157            &st[4..20],
9158            &[
9159                0x00, 0xeb, 0x0c, 0x0c, // ADD.W ip, r0, ip
9160                0x0c, 0xeb, 0x0b, 0x0c, // ADD.W ip, ip, fp
9161                0xcc, 0xf8, 0x00, 0x20, // STR.W r2, [ip, #0]
9162                0xcc, 0xf8, 0x04, 0x30, // STR.W r3, [ip, #4]
9163            ],
9164            "large-offset i64.store must fold offset into ip and access [ip,#0]/[ip,#4]"
9165        );
9166
9167        // Small-offset (imm12) indexed access stays byte-identical (#372): the
9168        // effective base is a single `ADD.W ip, fp, r0` and the halves keep the
9169        // folded immediates — NO extra MOVW/ADD.
9170        let small = encoder
9171            .encode(&ArmOp::I64Ldr {
9172                rdlo: Reg::R0,
9173                rdhi: Reg::R1,
9174                addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 8),
9175            })
9176            .unwrap();
9177        assert_eq!(
9178            &small[0..4],
9179            &[0x0b, 0xeb, 0x00, 0x0c],
9180            "small-offset indexed i64 must keep the single ADD.W ip, fp, r0"
9181        );
9182        assert_eq!(small.len(), 12, "ADD.W + 2×LDR.W (offset folded in imm12)");
9183    }
9184
9185    #[test]
9186    fn test_encode_i64_str_thumb2() {
9187        let encoder = ArmEncoder::new_thumb2();
9188        let op = ArmOp::I64Str {
9189            rdlo: Reg::R0,
9190            rdhi: Reg::R1,
9191            addr: MemAddr::imm(Reg::SP, 0),
9192        };
9193        let code = encoder.encode(&op).unwrap();
9194        // Two STR instructions (lo at offset, hi at offset+4)
9195        assert!(code.len() >= 4, "I64Str should emit at least 4 bytes");
9196    }
9197
9198    #[test]
9199    fn test_encode_i64_all_comparisons_thumb2() {
9200        let encoder = ArmEncoder::new_thumb2();
9201
9202        let ops = vec![
9203            ArmOp::I64Ne {
9204                rd: Reg::R0,
9205                rnlo: Reg::R0,
9206                rnhi: Reg::R1,
9207                rmlo: Reg::R2,
9208                rmhi: Reg::R3,
9209            },
9210            ArmOp::I64LtS {
9211                rd: Reg::R0,
9212                rnlo: Reg::R0,
9213                rnhi: Reg::R1,
9214                rmlo: Reg::R2,
9215                rmhi: Reg::R3,
9216            },
9217            ArmOp::I64LtU {
9218                rd: Reg::R0,
9219                rnlo: Reg::R0,
9220                rnhi: Reg::R1,
9221                rmlo: Reg::R2,
9222                rmhi: Reg::R3,
9223            },
9224            ArmOp::I64LeS {
9225                rd: Reg::R0,
9226                rnlo: Reg::R0,
9227                rnhi: Reg::R1,
9228                rmlo: Reg::R2,
9229                rmhi: Reg::R3,
9230            },
9231            ArmOp::I64LeU {
9232                rd: Reg::R0,
9233                rnlo: Reg::R0,
9234                rnhi: Reg::R1,
9235                rmlo: Reg::R2,
9236                rmhi: Reg::R3,
9237            },
9238            ArmOp::I64GtS {
9239                rd: Reg::R0,
9240                rnlo: Reg::R0,
9241                rnhi: Reg::R1,
9242                rmlo: Reg::R2,
9243                rmhi: Reg::R3,
9244            },
9245            ArmOp::I64GtU {
9246                rd: Reg::R0,
9247                rnlo: Reg::R0,
9248                rnhi: Reg::R1,
9249                rmlo: Reg::R2,
9250                rmhi: Reg::R3,
9251            },
9252            ArmOp::I64GeS {
9253                rd: Reg::R0,
9254                rnlo: Reg::R0,
9255                rnhi: Reg::R1,
9256                rmlo: Reg::R2,
9257                rmhi: Reg::R3,
9258            },
9259            ArmOp::I64GeU {
9260                rd: Reg::R0,
9261                rnlo: Reg::R0,
9262                rnhi: Reg::R1,
9263                rmlo: Reg::R2,
9264                rmhi: Reg::R3,
9265            },
9266        ];
9267
9268        for op in &ops {
9269            let code = encoder.encode(op).unwrap();
9270            assert!(
9271                code.len() >= 8,
9272                "i64 comparison {:?} should emit at least 8 bytes, got {}",
9273                op,
9274                code.len()
9275            );
9276        }
9277    }
9278
9279    #[test]
9280    fn test_encode_i64_const_zero_thumb2() {
9281        let encoder = ArmEncoder::new_thumb2();
9282        let op = ArmOp::I64Const {
9283            rdlo: Reg::R0,
9284            rdhi: Reg::R1,
9285            value: 0,
9286        };
9287        let code = encoder.encode(&op).unwrap();
9288        // MOVW R0, #0 (4 bytes) + MOVW R1, #0 (4 bytes) = 8 bytes
9289        assert_eq!(code.len(), 8, "I64Const(0) should be 8 bytes");
9290    }
9291
9292    #[test]
9293    fn test_encode_i64_const_negative_one_thumb2() {
9294        let encoder = ArmEncoder::new_thumb2();
9295        let op = ArmOp::I64Const {
9296            rdlo: Reg::R0,
9297            rdhi: Reg::R1,
9298            value: -1, // 0xFFFF_FFFF_FFFF_FFFF
9299        };
9300        let code = encoder.encode(&op).unwrap();
9301        // MOVW + MOVT for lo (8 bytes) + MOVW + MOVT for hi (8 bytes) = 16 bytes
9302        assert_eq!(code.len(), 16, "I64Const(-1) should be 16 bytes");
9303    }
9304
9305    // =========================================================================
9306    // Sub-word load/store encoding tests
9307    // =========================================================================
9308
9309    #[test]
9310    fn test_encode_ldrb_arm32() {
9311        let encoder = ArmEncoder::new_arm32();
9312        let op = ArmOp::Ldrb {
9313            rd: Reg::R0,
9314            addr: MemAddr::imm(Reg::R1, 4),
9315        };
9316        let code = encoder.encode(&op).unwrap();
9317        assert_eq!(code.len(), 4, "ARM32 LDRB should be 4 bytes");
9318        // LDRB R0, [R1, #4] = 0xE5D10004
9319        let encoded = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
9320        assert_eq!(encoded, 0xE5D10004, "Should encode LDRB R0, [R1, #4]");
9321    }
9322
9323    #[test]
9324    fn test_encode_strb_arm32() {
9325        let encoder = ArmEncoder::new_arm32();
9326        let op = ArmOp::Strb {
9327            rd: Reg::R0,
9328            addr: MemAddr::imm(Reg::R1, 0),
9329        };
9330        let code = encoder.encode(&op).unwrap();
9331        assert_eq!(code.len(), 4, "ARM32 STRB should be 4 bytes");
9332        // STRB R0, [R1, #0] = 0xE5C10000
9333        let encoded = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
9334        assert_eq!(encoded, 0xE5C10000, "Should encode STRB R0, [R1, #0]");
9335    }
9336
9337    #[test]
9338    fn test_encode_ldrh_arm32() {
9339        let encoder = ArmEncoder::new_arm32();
9340        let op = ArmOp::Ldrh {
9341            rd: Reg::R0,
9342            addr: MemAddr::imm(Reg::R1, 2),
9343        };
9344        let code = encoder.encode(&op).unwrap();
9345        assert_eq!(code.len(), 4, "ARM32 LDRH should be 4 bytes");
9346    }
9347
9348    #[test]
9349    fn test_encode_strh_arm32() {
9350        let encoder = ArmEncoder::new_arm32();
9351        let op = ArmOp::Strh {
9352            rd: Reg::R0,
9353            addr: MemAddr::imm(Reg::R1, 0),
9354        };
9355        let code = encoder.encode(&op).unwrap();
9356        assert_eq!(code.len(), 4, "ARM32 STRH should be 4 bytes");
9357    }
9358
9359    #[test]
9360    fn test_encode_ldrsb_arm32() {
9361        let encoder = ArmEncoder::new_arm32();
9362        let op = ArmOp::Ldrsb {
9363            rd: Reg::R0,
9364            addr: MemAddr::imm(Reg::R1, 0),
9365        };
9366        let code = encoder.encode(&op).unwrap();
9367        assert_eq!(code.len(), 4, "ARM32 LDRSB should be 4 bytes");
9368    }
9369
9370    #[test]
9371    fn test_encode_ldrsh_arm32() {
9372        let encoder = ArmEncoder::new_arm32();
9373        let op = ArmOp::Ldrsh {
9374            rd: Reg::R0,
9375            addr: MemAddr::imm(Reg::R1, 0),
9376        };
9377        let code = encoder.encode(&op).unwrap();
9378        assert_eq!(code.len(), 4, "ARM32 LDRSH should be 4 bytes");
9379    }
9380
9381    #[test]
9382    fn test_encode_ldrb_thumb2_16bit() {
9383        let encoder = ArmEncoder::new_thumb2();
9384        let op = ArmOp::Ldrb {
9385            rd: Reg::R0,
9386            addr: MemAddr::imm(Reg::R1, 4),
9387        };
9388        let code = encoder.encode(&op).unwrap();
9389        // Low registers + small offset -> 16-bit encoding
9390        assert_eq!(
9391            code.len(),
9392            2,
9393            "Thumb-2 LDRB with small offset should be 16-bit"
9394        );
9395    }
9396
9397    #[test]
9398    fn test_encode_ldrb_thumb2_32bit() {
9399        let encoder = ArmEncoder::new_thumb2();
9400        let op = ArmOp::Ldrb {
9401            rd: Reg::R0,
9402            addr: MemAddr::imm(Reg::R1, 100), // offset > 31 needs 32-bit
9403        };
9404        let code = encoder.encode(&op).unwrap();
9405        assert_eq!(
9406            code.len(),
9407            4,
9408            "Thumb-2 LDRB with large offset should be 32-bit"
9409        );
9410    }
9411
9412    #[test]
9413    fn test_encode_strb_thumb2_16bit() {
9414        let encoder = ArmEncoder::new_thumb2();
9415        let op = ArmOp::Strb {
9416            rd: Reg::R0,
9417            addr: MemAddr::imm(Reg::R1, 10),
9418        };
9419        let code = encoder.encode(&op).unwrap();
9420        assert_eq!(
9421            code.len(),
9422            2,
9423            "Thumb-2 STRB with small offset should be 16-bit"
9424        );
9425    }
9426
9427    #[test]
9428    fn test_encode_ldrh_thumb2_16bit() {
9429        let encoder = ArmEncoder::new_thumb2();
9430        let op = ArmOp::Ldrh {
9431            rd: Reg::R0,
9432            addr: MemAddr::imm(Reg::R1, 4), // offset aligned to 2, <= 62
9433        };
9434        let code = encoder.encode(&op).unwrap();
9435        assert_eq!(
9436            code.len(),
9437            2,
9438            "Thumb-2 LDRH with small aligned offset should be 16-bit"
9439        );
9440    }
9441
9442    #[test]
9443    fn test_encode_strh_thumb2_16bit() {
9444        let encoder = ArmEncoder::new_thumb2();
9445        let op = ArmOp::Strh {
9446            rd: Reg::R0,
9447            addr: MemAddr::imm(Reg::R1, 4),
9448        };
9449        let code = encoder.encode(&op).unwrap();
9450        assert_eq!(
9451            code.len(),
9452            2,
9453            "Thumb-2 STRH with small aligned offset should be 16-bit"
9454        );
9455    }
9456
9457    #[test]
9458    fn test_encode_ldrsb_thumb2() {
9459        let encoder = ArmEncoder::new_thumb2();
9460        let op = ArmOp::Ldrsb {
9461            rd: Reg::R0,
9462            addr: MemAddr::imm(Reg::R1, 0),
9463        };
9464        let code = encoder.encode(&op).unwrap();
9465        // LDRSB has no 16-bit immediate form, always 32-bit
9466        assert_eq!(code.len(), 4, "Thumb-2 LDRSB should be 32-bit");
9467    }
9468
9469    #[test]
9470    fn test_encode_ldrsh_thumb2() {
9471        let encoder = ArmEncoder::new_thumb2();
9472        let op = ArmOp::Ldrsh {
9473            rd: Reg::R0,
9474            addr: MemAddr::imm(Reg::R1, 0),
9475        };
9476        let code = encoder.encode(&op).unwrap();
9477        assert_eq!(code.len(), 4, "Thumb-2 LDRSH should be 32-bit");
9478    }
9479
9480    #[test]
9481    fn test_encode_memory_size_thumb2() {
9482        let encoder = ArmEncoder::new_thumb2();
9483        let op = ArmOp::MemorySize { rd: Reg::R0 };
9484        let code = encoder.encode(&op).unwrap();
9485        // R0 and R10 are not both low registers, so this needs careful handling
9486        assert!(!code.is_empty(), "MemorySize should produce code");
9487    }
9488
9489    #[test]
9490    fn test_encode_memory_grow_thumb2() {
9491        let encoder = ArmEncoder::new_thumb2();
9492        let op = ArmOp::MemoryGrow {
9493            rd: Reg::R0,
9494            rn: Reg::R0,
9495        };
9496        let code = encoder.encode(&op).unwrap();
9497        assert_eq!(code.len(), 4, "MemoryGrow (MVN) should be 32-bit Thumb-2");
9498    }
9499
9500    #[test]
9501    fn test_encode_subword_reg_offset_thumb2() {
9502        let encoder = ArmEncoder::new_thumb2();
9503
9504        // LDRB with register offset
9505        let op = ArmOp::Ldrb {
9506            rd: Reg::R0,
9507            addr: MemAddr::reg(Reg::R1, Reg::R2),
9508        };
9509        let code = encoder.encode(&op).unwrap();
9510        assert_eq!(
9511            code.len(),
9512            4,
9513            "Thumb-2 LDRB with reg offset should be 32-bit"
9514        );
9515
9516        // STRB with register offset
9517        let op = ArmOp::Strb {
9518            rd: Reg::R0,
9519            addr: MemAddr::reg(Reg::R1, Reg::R2),
9520        };
9521        let code = encoder.encode(&op).unwrap();
9522        assert_eq!(
9523            code.len(),
9524            4,
9525            "Thumb-2 STRB with reg offset should be 32-bit"
9526        );
9527
9528        // LDRH with register offset
9529        let op = ArmOp::Ldrh {
9530            rd: Reg::R0,
9531            addr: MemAddr::reg(Reg::R1, Reg::R2),
9532        };
9533        let code = encoder.encode(&op).unwrap();
9534        assert_eq!(
9535            code.len(),
9536            4,
9537            "Thumb-2 LDRH with reg offset should be 32-bit"
9538        );
9539
9540        // STRH with register offset
9541        let op = ArmOp::Strh {
9542            rd: Reg::R0,
9543            addr: MemAddr::reg(Reg::R1, Reg::R2),
9544        };
9545        let code = encoder.encode(&op).unwrap();
9546        assert_eq!(
9547            code.len(),
9548            4,
9549            "Thumb-2 STRH with reg offset should be 32-bit"
9550        );
9551    }
9552
9553    #[test]
9554    fn test_encode_subword_reg_imm_offset_thumb2() {
9555        let encoder = ArmEncoder::new_thumb2();
9556
9557        // LDRB with both register and immediate offset
9558        let op = ArmOp::Ldrb {
9559            rd: Reg::R0,
9560            addr: MemAddr::reg_imm(Reg::R1, Reg::R2, 4),
9561        };
9562        let code = encoder.encode(&op).unwrap();
9563        // ADD R12, R2, #4 (4 bytes) + LDRB R0, [R1, R12] (4 bytes) = 8 bytes
9564        assert_eq!(
9565            code.len(),
9566            8,
9567            "Thumb-2 LDRB with reg+imm offset should be 8 bytes"
9568        );
9569    }
9570
9571    // ========================================================================
9572    // Helium MVE encoding tests
9573    // ========================================================================
9574
9575    #[test]
9576    fn test_encode_mve_addi32_thumb2() {
9577        let encoder = ArmEncoder::new_thumb2();
9578        let op = ArmOp::MveAddI {
9579            qd: QReg::Q0,
9580            qn: QReg::Q1,
9581            qm: QReg::Q2,
9582            size: MveSize::S32,
9583        };
9584        let code = encoder.encode(&op).unwrap();
9585        assert_eq!(
9586            code.len(),
9587            4,
9588            "MVE VADD.I32 should be 4 bytes (Thumb-2 32-bit)"
9589        );
9590    }
9591
9592    #[test]
9593    fn test_encode_mve_subi16_thumb2() {
9594        let encoder = ArmEncoder::new_thumb2();
9595        let op = ArmOp::MveSubI {
9596            qd: QReg::Q0,
9597            qn: QReg::Q1,
9598            qm: QReg::Q2,
9599            size: MveSize::S16,
9600        };
9601        let code = encoder.encode(&op).unwrap();
9602        assert_eq!(code.len(), 4, "MVE VSUB.I16 should be 4 bytes");
9603    }
9604
9605    #[test]
9606    fn test_encode_mve_muli8_thumb2() {
9607        let encoder = ArmEncoder::new_thumb2();
9608        let op = ArmOp::MveMulI {
9609            qd: QReg::Q0,
9610            qn: QReg::Q1,
9611            qm: QReg::Q2,
9612            size: MveSize::S8,
9613        };
9614        let code = encoder.encode(&op).unwrap();
9615        assert_eq!(code.len(), 4, "MVE VMUL.I8 should be 4 bytes");
9616    }
9617
9618    #[test]
9619    fn test_encode_mve_bitwise_thumb2() {
9620        let encoder = ArmEncoder::new_thumb2();
9621
9622        let ops = vec![
9623            ArmOp::MveAnd {
9624                qd: QReg::Q0,
9625                qn: QReg::Q1,
9626                qm: QReg::Q2,
9627            },
9628            ArmOp::MveOrr {
9629                qd: QReg::Q0,
9630                qn: QReg::Q1,
9631                qm: QReg::Q2,
9632            },
9633            ArmOp::MveEor {
9634                qd: QReg::Q0,
9635                qn: QReg::Q1,
9636                qm: QReg::Q2,
9637            },
9638            ArmOp::MveBic {
9639                qd: QReg::Q0,
9640                qn: QReg::Q1,
9641                qm: QReg::Q2,
9642            },
9643        ];
9644        for op in ops {
9645            let code = encoder.encode(&op).unwrap();
9646            assert_eq!(code.len(), 4, "MVE bitwise op should be 4 bytes");
9647        }
9648    }
9649
9650    #[test]
9651    fn test_encode_mve_mvn_thumb2() {
9652        let encoder = ArmEncoder::new_thumb2();
9653        let op = ArmOp::MveMvn {
9654            qd: QReg::Q0,
9655            qm: QReg::Q1,
9656        };
9657        let code = encoder.encode(&op).unwrap();
9658        assert_eq!(code.len(), 4, "MVE VMVN should be 4 bytes");
9659    }
9660
9661    #[test]
9662    fn test_encode_mve_load_store_thumb2() {
9663        let encoder = ArmEncoder::new_thumb2();
9664
9665        let load = ArmOp::MveLoad {
9666            qd: QReg::Q0,
9667            addr: MemAddr::imm(Reg::R0, 16),
9668        };
9669        let code = encoder.encode(&load).unwrap();
9670        assert_eq!(code.len(), 4, "MVE VLDRW.32 should be 4 bytes");
9671
9672        let store = ArmOp::MveStore {
9673            qd: QReg::Q1,
9674            addr: MemAddr::imm(Reg::R1, 0),
9675        };
9676        let code = encoder.encode(&store).unwrap();
9677        assert_eq!(code.len(), 4, "MVE VSTRW.32 should be 4 bytes");
9678    }
9679
9680    #[test]
9681    fn test_encode_mve_const_thumb2() {
9682        let encoder = ArmEncoder::new_thumb2();
9683        let op = ArmOp::MveConst {
9684            qd: QReg::Q0,
9685            bytes: [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0],
9686        };
9687        let code = encoder.encode(&op).unwrap();
9688        // Should be 4 words of (MOVW R12 + VMOV Sn) = 4 * (4+4) = 32 bytes min
9689        // Some words with hi16=0 skip MOVT, so length varies
9690        assert!(
9691            code.len() >= 24,
9692            "MVE const should produce multiple instructions"
9693        );
9694    }
9695
9696    #[test]
9697    fn test_encode_mve_dup_thumb2() {
9698        let encoder = ArmEncoder::new_thumb2();
9699        let op = ArmOp::MveDup {
9700            qd: QReg::Q0,
9701            rn: Reg::R0,
9702            size: MveSize::S32,
9703        };
9704        let code = encoder.encode(&op).unwrap();
9705        assert_eq!(code.len(), 4, "MVE VDUP.32 should be 4 bytes");
9706    }
9707
9708    #[test]
9709    fn test_encode_mve_extract_lane_thumb2() {
9710        let encoder = ArmEncoder::new_thumb2();
9711        let op = ArmOp::MveExtractLane {
9712            rd: Reg::R0,
9713            qn: QReg::Q1,
9714            lane: 2,
9715            size: MveSize::S32,
9716        };
9717        let code = encoder.encode(&op).unwrap();
9718        assert_eq!(code.len(), 4, "MVE extract lane should be 4 bytes");
9719    }
9720
9721    #[test]
9722    fn test_encode_mve_insert_lane_thumb2() {
9723        let encoder = ArmEncoder::new_thumb2();
9724        let op = ArmOp::MveInsertLane {
9725            qd: QReg::Q0,
9726            rn: Reg::R1,
9727            lane: 3,
9728            size: MveSize::S32,
9729        };
9730        let code = encoder.encode(&op).unwrap();
9731        assert_eq!(code.len(), 4, "MVE insert lane should be 4 bytes");
9732    }
9733
9734    #[test]
9735    fn test_encode_mve_addf32_thumb2() {
9736        let encoder = ArmEncoder::new_thumb2();
9737        let op = ArmOp::MveAddF32 {
9738            qd: QReg::Q0,
9739            qn: QReg::Q1,
9740            qm: QReg::Q2,
9741        };
9742        let code = encoder.encode(&op).unwrap();
9743        assert_eq!(code.len(), 4, "MVE VADD.F32 should be 4 bytes");
9744    }
9745
9746    #[test]
9747    fn test_encode_mve_divf32_thumb2() {
9748        let encoder = ArmEncoder::new_thumb2();
9749        let op = ArmOp::MveDivF32 {
9750            qd: QReg::Q0,
9751            qn: QReg::Q1,
9752            qm: QReg::Q2,
9753        };
9754        let code = encoder.encode(&op).unwrap();
9755        // Lane-wise: 4 x VDIV.F32 = 4 x 4 = 16 bytes
9756        assert_eq!(
9757            code.len(),
9758            16,
9759            "MVE VDIV.F32 (lane-wise) should be 16 bytes"
9760        );
9761    }
9762
9763    #[test]
9764    fn test_encode_mve_sqrtf32_thumb2() {
9765        let encoder = ArmEncoder::new_thumb2();
9766        let op = ArmOp::MveSqrtF32 {
9767            qd: QReg::Q0,
9768            qm: QReg::Q1,
9769        };
9770        let code = encoder.encode(&op).unwrap();
9771        // Lane-wise: 4 x VSQRT.F32 = 4 x 4 = 16 bytes
9772        assert_eq!(
9773            code.len(),
9774            16,
9775            "MVE VSQRT.F32 (lane-wise) should be 16 bytes"
9776        );
9777    }
9778
9779    #[test]
9780    fn test_encode_mve_negf32_thumb2() {
9781        let encoder = ArmEncoder::new_thumb2();
9782        let op = ArmOp::MveNegF32 {
9783            qd: QReg::Q0,
9784            qm: QReg::Q1,
9785        };
9786        let code = encoder.encode(&op).unwrap();
9787        assert_eq!(code.len(), 4, "MVE VNEG.F32 should be 4 bytes");
9788    }
9789
9790    #[test]
9791    fn test_encode_mve_absf32_thumb2() {
9792        let encoder = ArmEncoder::new_thumb2();
9793        let op = ArmOp::MveAbsF32 {
9794            qd: QReg::Q0,
9795            qm: QReg::Q1,
9796        };
9797        let code = encoder.encode(&op).unwrap();
9798        assert_eq!(code.len(), 4, "MVE VABS.F32 should be 4 bytes");
9799    }
9800
9801    /// VCR-RA-001 / immediate-folding precondition: pins the Thumb-2 `AND`
9802    /// immediate encoding for the byte range and documents its bound.
9803    ///
9804    /// The `And { Operand2::Imm }` encoder packs the low 12 bits straight into
9805    /// the `i:imm3:imm8` field WITHOUT applying ThumbExpandImm (the modified-
9806    /// immediate expansion). For `imm <= 0xFF` (e.g. gale's int8 clamps
9807    /// `#0x7e` / `#0x7f`) that is correct — `i:imm3 = 0000` means "imm8
9808    /// zero-extended". So `and r2, r0, #0x7e` encodes to the canonical
9809    /// `00 f0 7e 02`. For `imm >= 0x100` the field would need a true
9810    /// ThumbExpandImm pattern (rotation / replication), which is NOT
9811    /// implemented here — so **immediate folding must gate on `imm <= 0xFF`**
9812    /// until the encoder is hardened to ThumbExpandImm/Ok-or-Err (the
9813    /// "encoder must be Ok-or-Err, never silently wrong" principle, #180/#185).
9814    /// This bound covers the measured `flat_flight` waste (#209).
9815    #[test]
9816    fn and_immediate_encodes_correctly_in_byte_range_documents_fold_bound() {
9817        let encoder = ArmEncoder::new_thumb2();
9818        let op = ArmOp::And {
9819            rd: Reg::R2,
9820            rn: Reg::R0,
9821            op2: Operand2::Imm(0x7e),
9822        };
9823        let code = encoder.encode(&op).unwrap();
9824        assert_eq!(
9825            code,
9826            vec![0x00, 0xf0, 0x7e, 0x02],
9827            "and r2, r0, #0x7e must encode to the canonical AND.W T1 (imm8=0x7e)"
9828        );
9829    }
9830
9831    /// #255: the shared ThumbExpandImm reverse-encoder underpinning the
9832    /// data-processing immediate fix. Encodable modified immediates round-trip to
9833    /// the expected `i:imm3:imm8` field; a genuinely non-modified value is `None`
9834    /// (caller must materialize into a register). Note `1000 = 0xFA ror 30` *is*
9835    /// representable (field 0xF7A) — the old encoder mis-encoded it (raw 0x3E8);
9836    /// this encodes it correctly.
9837    #[test]
9838    fn try_thumb_expand_imm_encodes_modified_immediates() {
9839        assert_eq!(try_thumb_expand_imm(0x7e), Some(0x07e)); // zero-extended byte
9840        assert_eq!(try_thumb_expand_imm(0xff), Some(0x0ff));
9841        assert_eq!(try_thumb_expand_imm(0x0001_0001), Some(0x101)); // 0x00XY00XY
9842        assert_eq!(try_thumb_expand_imm(0xff00_ff00), Some(0x2ff)); // 0xXY00XY00
9843        assert_eq!(try_thumb_expand_imm(0xffff_ffff), Some(0x3ff)); // 0xXYXYXYXY
9844        assert_eq!(try_thumb_expand_imm(0x100), Some(0xf80)); // 0x80 ror 31
9845        assert_eq!(try_thumb_expand_imm(0x8000_0000), Some(0x400)); // 0x80 ror 8
9846        assert_eq!(try_thumb_expand_imm(1000), Some(0xf7a)); // 0xFA ror 30
9847        // Genuinely unrepresentable (bits too far apart for an 8-bit window).
9848        assert_eq!(try_thumb_expand_imm(0x101), None);
9849        assert_eq!(try_thumb_expand_imm(0x12345), None);
9850    }
9851
9852    /// #255: CMP/ADDS/SUBS encode any valid modified immediate correctly, and
9853    /// ERROR (not silently mis-encode) on a genuinely unrepresentable one,
9854    /// forcing the selector to materialize into a register — closing the
9855    /// silent-miscompile class of #251/#253.
9856    #[test]
9857    fn cmp_adds_subs_immediate_error_on_non_modified_imm() {
9858        let encoder = ArmEncoder::new_thumb2();
9859        // cmp r0, #0xff → valid → Ok; cmp r0, #1000 → valid (0xFA ror 30) → Ok.
9860        assert!(encoder.encode_thumb32_cmp_imm(&Reg::R0, 0xff).is_ok());
9861        assert!(encoder.encode_thumb32_cmp_imm(&Reg::R0, 1000).is_ok());
9862        // cmp r0, #0x101 → NOT a modified immediate → Err (materialize-reg).
9863        assert!(
9864            encoder.encode_thumb32_cmp_imm(&Reg::R0, 0x101).is_err(),
9865            "cmp #0x101 must error, not compare the wrong constant"
9866        );
9867        assert!(
9868            encoder
9869                .encode_thumb32_adds(&Reg::R0, &Reg::R0, 0x101)
9870                .is_err()
9871        );
9872        assert!(
9873            encoder
9874                .encode_thumb32_subs(&Reg::R0, &Reg::R0, 0x101)
9875                .is_err()
9876        );
9877        // ...but a valid modified immediate still encodes.
9878        assert!(
9879            encoder
9880                .encode_thumb32_adds(&Reg::R0, &Reg::R0, 0x80)
9881                .is_ok()
9882        );
9883    }
9884
9885    /// #257: MLA (multiply-accumulate) encodes as MLS without the bit-4 op flag.
9886    /// `mla r2, r3, r4, r8` (rd=r2, rn=r3, rm=r4, ra=r8) → Thumb-2 `03 fb 04 82`.
9887    #[test]
9888    fn mla_thumb2_encodes_correctly() {
9889        let encoder = ArmEncoder::new_thumb2();
9890        let code = encoder
9891            .encode(&ArmOp::Mla {
9892                rd: Reg::R2,
9893                rn: Reg::R3,
9894                rm: Reg::R4,
9895                ra: Reg::R8,
9896            })
9897            .unwrap();
9898        // hw1 = 0xFB03, hw2 = (8<<12)|(2<<8)|4 = 0x8204
9899        assert_eq!(code, vec![0x03, 0xfb, 0x04, 0x82]);
9900    }
9901
9902    /// #259: LDR/STR (and sub-word) immediate-offset encoders truncated
9903    /// `offset & 0xFFF`, silently targeting the wrong address for offset >= 4096.
9904    /// They now error (the selector must use register-offset addressing) — the
9905    /// load/store sibling of the #253/#255 class. Offsets <= 4095 still encode.
9906    #[test]
9907    fn ldst_imm12_offset_errors_when_out_of_range() {
9908        let encoder = ArmEncoder::new_thumb2();
9909        // offset 0xFFF (4095): valid → Ok; ldr r0, [r1, #4095].
9910        assert!(
9911            encoder
9912                .encode_thumb32_ldr(&Reg::R0, &Reg::R1, 0xFFF)
9913                .is_ok()
9914        );
9915        // offset 0x1000 (4096): out of imm12 range → Err (not & 0xFFF → #0).
9916        assert!(
9917            encoder
9918                .encode_thumb32_ldr(&Reg::R0, &Reg::R1, 0x1000)
9919                .is_err(),
9920            "ldr offset 4096 must error, not wrap to 0"
9921        );
9922        assert!(
9923            encoder
9924                .encode_thumb32_str(&Reg::R0, &Reg::R1, 0x1000)
9925                .is_err()
9926        );
9927        assert!(
9928            encoder
9929                .encode_thumb32_ldrb_imm(&Reg::R0, &Reg::R1, 5000)
9930                .is_err()
9931        );
9932        assert!(
9933            encoder
9934                .encode_thumb32_strh_imm(&Reg::R0, &Reg::R1, 5000)
9935                .is_err()
9936        );
9937    }
9938
9939    /// Latent miscompile fix: ADD/SUB with a >0xFF immediate (e.g.
9940    /// `add sp, sp, #frame` for a >=256-byte frame) used ADD.W (T3), whose
9941    /// `i:imm3:imm8` is a ThumbExpandImm modified immediate — so `#256` silently
9942    /// encoded as `#0` (stack corruption). Use ADDW/SUBW (T4), a PLAIN 12-bit
9943    /// immediate, for 0x100..=0xFFF; keep T3 for <=0xFF (bit-identical); error
9944    /// beyond 4095.
9945    #[test]
9946    fn add_sub_large_immediate_use_addw_subw_not_misencoded() {
9947        let encoder = ArmEncoder::new_thumb2();
9948        // add sp, sp, #256  →  ADDW (T4) SP, SP, #256  =  0d f2 00 1d
9949        assert_eq!(
9950            encoder
9951                .encode(&ArmOp::Add {
9952                    rd: Reg::SP,
9953                    rn: Reg::SP,
9954                    op2: Operand2::Imm(256),
9955                })
9956                .unwrap(),
9957            vec![0x0d, 0xf2, 0x00, 0x1d],
9958            "add sp,sp,#256 must be ADDW (plain imm12), not a mis-encoded ADD.W"
9959        );
9960        // sub sp, sp, #256  →  SUBW (T4) SP, SP, #256  =  ad f2 00 1d
9961        assert_eq!(
9962            encoder
9963                .encode(&ArmOp::Sub {
9964                    rd: Reg::SP,
9965                    rn: Reg::SP,
9966                    op2: Operand2::Imm(256),
9967                })
9968                .unwrap(),
9969            vec![0xad, 0xf2, 0x00, 0x1d],
9970        );
9971        // > 4095 has no single-instruction encoding → error, not silent wrong.
9972        assert!(
9973            encoder
9974                .encode(&ArmOp::Add {
9975                    rd: Reg::SP,
9976                    rn: Reg::SP,
9977                    op2: Operand2::Imm(5000),
9978                })
9979                .is_err(),
9980            "add #5000 must error (no single ADDW), not mis-encode"
9981        );
9982    }
9983
9984    /// Closes the data-proc immediate class: AND and CMN now go through
9985    /// `try_thumb_expand_imm` like ORR/EOR/CMP — correct for any modified
9986    /// immediate, `Err` (not raw-pack / NOP) on an un-encodable one. The byte
9987    /// range stays bit-identical (`and r2,r0,#0x7e` is unchanged).
9988    #[test]
9989    fn and_cmn_immediate_thumb_expand_else_error() {
9990        let encoder = ArmEncoder::new_thumb2();
9991        // byte range unchanged (bit-identical with the pre-retrofit encoding)
9992        assert_eq!(
9993            encoder
9994                .encode(&ArmOp::And {
9995                    rd: Reg::R2,
9996                    rn: Reg::R0,
9997                    op2: Operand2::Imm(0x7e),
9998                })
9999                .unwrap(),
10000            vec![0x00, 0xf0, 0x7e, 0x02],
10001        );
10002        // a valid replicated modified immediate now encodes (was silently wrong)
10003        assert!(
10004            encoder
10005                .encode(&ArmOp::And {
10006                    rd: Reg::R2,
10007                    rn: Reg::R0,
10008                    op2: Operand2::Imm(0xff00ff00u32 as i32),
10009                })
10010                .is_ok()
10011        );
10012        // a genuinely un-encodable immediate errors (AND was raw-pack; CMN NOP)
10013        assert!(
10014            encoder
10015                .encode(&ArmOp::And {
10016                    rd: Reg::R2,
10017                    rn: Reg::R0,
10018                    op2: Operand2::Imm(0x101),
10019                })
10020                .is_err()
10021        );
10022        assert!(
10023            encoder
10024                .encode(&ArmOp::Cmn {
10025                    rn: Reg::R0,
10026                    op2: Operand2::Imm(0x101),
10027                })
10028                .is_err(),
10029            "CMN #0x101 must error, not emit a NOP"
10030        );
10031    }
10032
10033    /// VCR-RA-001: ORR/EOR with a small immediate must encode the real
10034    /// instruction (not a silent `0xBF00` NOP). Pins the byte range and the
10035    /// Ok-or-Err bound that makes future Or/Eor immediate folding safe.
10036    #[test]
10037    fn orr_eor_immediate_encode_in_byte_range_else_error() {
10038        let encoder = ArmEncoder::new_thumb2();
10039        // orr r2, r0, #0x7e  →  ORR.W T1, imm8=0x7e
10040        assert_eq!(
10041            encoder
10042                .encode(&ArmOp::Orr {
10043                    rd: Reg::R2,
10044                    rn: Reg::R0,
10045                    op2: Operand2::Imm(0x7e),
10046                })
10047                .unwrap(),
10048            vec![0x40, 0xf0, 0x7e, 0x02],
10049        );
10050        // eor r2, r0, #0x7e  →  EOR.W T1, imm8=0x7e
10051        assert_eq!(
10052            encoder
10053                .encode(&ArmOp::Eor {
10054                    rd: Reg::R2,
10055                    rn: Reg::R0,
10056                    op2: Operand2::Imm(0x7e),
10057                })
10058                .unwrap(),
10059            vec![0x80, 0xf0, 0x7e, 0x02],
10060        );
10061        // Out-of-range immediates error rather than silently mis-encode / NOP.
10062        assert!(
10063            encoder
10064                .encode(&ArmOp::Orr {
10065                    rd: Reg::R2,
10066                    rn: Reg::R0,
10067                    op2: Operand2::Imm(0x140),
10068                })
10069                .is_err(),
10070            "ORR #0x140 must error, not emit a NOP"
10071        );
10072    }
10073
10074    #[test]
10075    fn test_encode_mve_different_qregs() {
10076        let encoder = ArmEncoder::new_thumb2();
10077
10078        // Test that different Q-register numbers produce different encodings
10079        let op1 = ArmOp::MveAddI {
10080            qd: QReg::Q0,
10081            qn: QReg::Q0,
10082            qm: QReg::Q0,
10083            size: MveSize::S32,
10084        };
10085        let op2 = ArmOp::MveAddI {
10086            qd: QReg::Q3,
10087            qn: QReg::Q5,
10088            qm: QReg::Q7,
10089            size: MveSize::S32,
10090        };
10091        let code1 = encoder.encode(&op1).unwrap();
10092        let code2 = encoder.encode(&op2).unwrap();
10093        assert_ne!(
10094            code1, code2,
10095            "Different Q-registers should produce different encodings"
10096        );
10097    }
10098
10099    #[test]
10100    fn test_encode_mve_arm32_nop() {
10101        // MVE instructions on ARM32 encoder should produce NOP (only Thumb-2 supported)
10102        let encoder = ArmEncoder::new_arm32();
10103        let op = ArmOp::MveAddI {
10104            qd: QReg::Q0,
10105            qn: QReg::Q1,
10106            qm: QReg::Q2,
10107            size: MveSize::S32,
10108        };
10109        let code = encoder.encode(&op).unwrap();
10110        assert_eq!(code.len(), 4, "ARM32 MVE should be 4 bytes (NOP)");
10111        // NOP in ARM32 is 0xE1A00000 (MOV R0, R0)
10112        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
10113        assert_eq!(instr, 0xE1A00000, "ARM32 MVE should encode as NOP");
10114    }
10115}