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 (#610 rewrite).
3555            // For n < 32: new_hi = (hi << n) | (lo >> (32-n)), new_lo = (lo << n) | (hi >> (32-n))
3556            // For n >= 32: same formula with lo/hi swapped, shift by m = n-32.
3557            //
3558            // Fixed-reg core: value in R0:R1, amount in R2, scratch R3 + R12
3559            // (all four saved/marshaled by the #610 fixed-ABI wrapper; the
3560            // pre-#610 expansion wrote through the selector's registers with
3561            // colliding R3/R4 scratch and restored the saved R4 OVER the
3562            // result). Relies on ARM register-shift semantics: amounts >= 32
3563            // yield 0 for LSL/LSR, which makes n = 0 and n = 32 exact.
3564            ArmOp::I64Rotl {
3565                rdlo,
3566                rdhi,
3567                rnlo,
3568                rnhi,
3569                shift,
3570            } => {
3571                let mut bytes = Vec::new();
3572                emit_i64_fixed_abi_entry(&mut bytes, &[rnlo, rnhi, shift]);
3573
3574                let core: [u16; 35] = [
3575                    0xF002, 0x023F, // AND.W  R2, R2, #63   (mask amount mod 64)
3576                    0xF1B2, 0x0320, // SUBS.W R3, R2, #32   (R3 = n-32, sets N)
3577                    0xD50E, //         BPL    .large        (n >= 32)
3578                    // --- small rotation (n < 32) ---
3579                    0xF1C2, 0x0320, // RSB.W  R3, R2, #32   (R3 = 32-n)
3580                    0xFA20, 0xFC03, // LSR.W  R12, R0, R3   (lo >> (32-n))
3581                    0xFA21, 0xF303, // LSR.W  R3, R1, R3    (hi >> (32-n))
3582                    0xFA01, 0xF102, // LSL.W  R1, R1, R2    (hi << n)
3583                    0xEA41, 0x010C, // ORR.W  R1, R1, R12   (new_hi)
3584                    0xFA00, 0xF002, // LSL.W  R0, R0, R2    (lo << n)
3585                    0xEA40, 0x0003, // ORR.W  R0, R0, R3    (new_lo)
3586                    0xE00E, //         B      .done
3587                    // --- large rotation (n >= 32), R3 = m = n-32 ---
3588                    0xF1C3, 0x0220, // RSB.W  R2, R3, #32   (R2 = 32-m = 64-n)
3589                    0xFA21, 0xFC02, // LSR.W  R12, R1, R2   (hi >> (64-n))
3590                    0xFA20, 0xF202, // LSR.W  R2, R0, R2    (lo >> (64-n))
3591                    0xFA00, 0xF003, // LSL.W  R0, R0, R3    (lo << m)
3592                    0xFA01, 0xF103, // LSL.W  R1, R1, R3    (hi << m)
3593                    0xEA40, 0x0C0C, // ORR.W  R12, R0, R12  (new_hi = (lo<<m)|(hi>>(64-n)))
3594                    0xEA41, 0x0002, // ORR.W  R0, R1, R2    (new_lo = (hi<<m)|(lo>>(64-n)))
3595                    0x4661, //         MOV    R1, R12       (new_hi into place)
3596                            // .done: result in R0:R1
3597                ];
3598                for hw in core {
3599                    bytes.extend_from_slice(&hw.to_le_bytes());
3600                }
3601
3602                emit_i64_fixed_abi_exit(&mut bytes, rdlo, rdhi)?;
3603                Ok(bytes) // Total: 102 bytes
3604            }
3605
3606            // I64Rotr: 64-bit rotate right (#610 rewrite).
3607            // For n < 32: new_lo = (lo >> n) | (hi << (32-n)), new_hi = (hi >> n) | (lo << (32-n))
3608            // For n >= 32: same formula with lo/hi swapped, shift by m = n-32.
3609            //
3610            // Same fixed-reg core contract as I64Rotl: value in R0:R1, amount
3611            // in R2, scratch R3 + R12, all covered by the fixed-ABI wrapper.
3612            ArmOp::I64Rotr {
3613                rdlo,
3614                rdhi,
3615                rnlo,
3616                rnhi,
3617                shift,
3618            } => {
3619                let mut bytes = Vec::new();
3620                emit_i64_fixed_abi_entry(&mut bytes, &[rnlo, rnhi, shift]);
3621
3622                let core: [u16; 35] = [
3623                    0xF002, 0x023F, // AND.W  R2, R2, #63   (mask amount mod 64)
3624                    0xF1B2, 0x0320, // SUBS.W R3, R2, #32   (R3 = n-32, sets N)
3625                    0xD50E, //         BPL    .large        (n >= 32)
3626                    // --- small rotation (n < 32) ---
3627                    0xF1C2, 0x0320, // RSB.W  R3, R2, #32   (R3 = 32-n)
3628                    0xFA01, 0xFC03, // LSL.W  R12, R1, R3   (hi << (32-n))
3629                    0xFA00, 0xF303, // LSL.W  R3, R0, R3    (lo << (32-n))
3630                    0xFA20, 0xF002, // LSR.W  R0, R0, R2    (lo >> n)
3631                    0xEA40, 0x000C, // ORR.W  R0, R0, R12   (new_lo)
3632                    0xFA21, 0xF102, // LSR.W  R1, R1, R2    (hi >> n)
3633                    0xEA41, 0x0103, // ORR.W  R1, R1, R3    (new_hi)
3634                    0xE00E, //         B      .done
3635                    // --- large rotation (n >= 32), R3 = m = n-32 ---
3636                    0xF1C3, 0x0220, // RSB.W  R2, R3, #32   (R2 = 32-m = 64-n)
3637                    0xFA00, 0xFC02, // LSL.W  R12, R0, R2   (lo << (64-n))
3638                    0xFA01, 0xF202, // LSL.W  R2, R1, R2    (hi << (64-n))
3639                    0xFA21, 0xF103, // LSR.W  R1, R1, R3    (hi >> m)
3640                    0xEA41, 0x0C0C, // ORR.W  R12, R1, R12  (new_lo = (hi>>m)|(lo<<(64-n)))
3641                    0xFA20, 0xF103, // LSR.W  R1, R0, R3    (lo >> m)
3642                    0xEA41, 0x0102, // ORR.W  R1, R1, R2    (new_hi = (lo>>m)|(hi<<(64-n)))
3643                    0x4660, //         MOV    R0, R12       (new_lo into place)
3644                            // .done: result in R0:R1
3645                ];
3646                for hw in core {
3647                    bytes.extend_from_slice(&hw.to_le_bytes());
3648                }
3649
3650                emit_i64_fixed_abi_exit(&mut bytes, rdlo, rdhi)?;
3651                Ok(bytes) // Total: 102 bytes
3652            }
3653
3654            // I64Clz: Count leading zeros in 64-bit value
3655            // If hi != 0: result = CLZ(hi)
3656            // If hi == 0: result = 32 + CLZ(lo)
3657            //
3658            // Layout (using CMP+BNE approach for consistency):
3659            // 0: CMP.W rnhi, #0 (4 bytes)
3660            // 4: BEQ .hi_zero (2 bytes) - branch forward to offset 14
3661            // 6: CLZ.W rd, rnhi (4 bytes)
3662            // 10: B .done (2 bytes) - branch forward to offset 22
3663            // 12: NOP (2 bytes) - padding for alignment
3664            // 14: .hi_zero: CLZ.W rd, rnlo (4 bytes)
3665            // 18: ADD.W rd, rd, #32 (4 bytes)
3666            // 22: .done
3667            ArmOp::I64Clz { rd, rnlo, rnhi } => {
3668                let rd_bits = reg_to_bits(rd);
3669                let rn_lo_bits = reg_to_bits(rnlo);
3670                let rn_hi_bits = reg_to_bits(rnhi);
3671                let mut bytes = Vec::new();
3672
3673                // CMP.W rnhi, #0 (4 bytes at offset 0)
3674                let hw1: u16 = (0xF1B0 | rn_hi_bits) as u16;
3675                let hw2: u16 = 0x0F00;
3676                bytes.extend_from_slice(&hw1.to_le_bytes());
3677                bytes.extend_from_slice(&hw2.to_le_bytes());
3678
3679                // BEQ .hi_zero (2 bytes at offset 4)
3680                // PC = 4 + 4 = 8, target = 14, offset = 6, imm8 = 3
3681                let beq: u16 = 0xD003;
3682                bytes.extend_from_slice(&beq.to_le_bytes());
3683
3684                // CLZ.W rd, rnhi (4 bytes at offset 6)
3685                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3686                let hw1: u16 = (0xFAB0 | rn_hi_bits) as u16;
3687                let hw2: u16 = (0xF080 | (rd_bits << 8) | rn_hi_bits) as u16;
3688                bytes.extend_from_slice(&hw1.to_le_bytes());
3689                bytes.extend_from_slice(&hw2.to_le_bytes());
3690
3691                // B .done (2 bytes at offset 10)
3692                // PC = 10 + 4 = 14, target = 22, offset = 8, imm11 = 4
3693                let b_done: u16 = 0xE004;
3694                bytes.extend_from_slice(&b_done.to_le_bytes());
3695
3696                // NOP (2 bytes at offset 12) - padding
3697                bytes.extend_from_slice(&0xBF00u16.to_le_bytes());
3698
3699                // .hi_zero: (offset 14)
3700                // CLZ.W rd, rnlo (4 bytes)
3701                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3702                let hw1: u16 = (0xFAB0 | rn_lo_bits) as u16;
3703                let hw2: u16 = (0xF080 | (rd_bits << 8) | rn_lo_bits) as u16;
3704                bytes.extend_from_slice(&hw1.to_le_bytes());
3705                bytes.extend_from_slice(&hw2.to_le_bytes());
3706
3707                // ADD.W rd, rd, #32 (4 bytes at offset 18)
3708                let hw1: u16 = (0xF100 | rd_bits) as u16;
3709                let hw2: u16 = ((rd_bits << 8) | 0x20) as u16;
3710                bytes.extend_from_slice(&hw1.to_le_bytes());
3711                bytes.extend_from_slice(&hw2.to_le_bytes());
3712
3713                // .done: (offset 22)
3714                // i64.clz returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
3715                // MOVS Rn, #0: 0010 0 Rn 00000000
3716                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
3717                bytes.extend_from_slice(&mov0.to_le_bytes());
3718
3719                Ok(bytes)
3720            }
3721
3722            // I64Ctz: Count trailing zeros in 64-bit value
3723            // If lo != 0: result = CTZ(lo) = CLZ(RBIT(lo))
3724            // If lo == 0: result = 32 + CTZ(hi) = 32 + CLZ(RBIT(hi))
3725            //
3726            // Layout:
3727            // 0: CMP.W rnlo, #0 (4 bytes)
3728            // 4: BEQ .lo_zero (2 bytes) - branch to offset 18
3729            // 6: RBIT.W rd, rnlo (4 bytes)
3730            // 10: CLZ.W rd, rd (4 bytes)
3731            // 14: B .done (2 bytes) - branch to offset 30
3732            // 16: NOP (2 bytes) - padding
3733            // 18: .lo_zero: RBIT.W rd, rnhi (4 bytes)
3734            // 22: CLZ.W rd, rd (4 bytes)
3735            // 26: ADD.W rd, rd, #32 (4 bytes)
3736            // 30: .done
3737            ArmOp::I64Ctz { rd, rnlo, rnhi } => {
3738                let rd_bits = reg_to_bits(rd);
3739                let rn_lo_bits = reg_to_bits(rnlo);
3740                let rn_hi_bits = reg_to_bits(rnhi);
3741                let mut bytes = Vec::new();
3742
3743                // CMP.W rnlo, #0 (4 bytes at offset 0)
3744                let hw1: u16 = (0xF1B0 | rn_lo_bits) as u16;
3745                let hw2: u16 = 0x0F00;
3746                bytes.extend_from_slice(&hw1.to_le_bytes());
3747                bytes.extend_from_slice(&hw2.to_le_bytes());
3748
3749                // BEQ .lo_zero (2 bytes at offset 4)
3750                // PC = 4 + 4 = 8, target = 18, offset = 10, imm8 = 5
3751                let beq: u16 = 0xD005;
3752                bytes.extend_from_slice(&beq.to_le_bytes());
3753
3754                // RBIT.W rd, rnlo (4 bytes at offset 6)
3755                // RBIT T1: hw1 = 0xFA9<Rm>, hw2 = 0xF<Rd>A<Rm>
3756                let hw1: u16 = (0xFA90 | rn_lo_bits) as u16;
3757                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rn_lo_bits) as u16;
3758                bytes.extend_from_slice(&hw1.to_le_bytes());
3759                bytes.extend_from_slice(&hw2.to_le_bytes());
3760
3761                // CLZ.W rd, rd (4 bytes at offset 10)
3762                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3763                let hw1: u16 = (0xFAB0 | rd_bits) as u16;
3764                let hw2: u16 = (0xF080 | (rd_bits << 8) | rd_bits) as u16;
3765                bytes.extend_from_slice(&hw1.to_le_bytes());
3766                bytes.extend_from_slice(&hw2.to_le_bytes());
3767
3768                // B .done (2 bytes at offset 14)
3769                // PC = 14 + 4 = 18, target = 30, offset = 12, imm11 = 6
3770                let b_done: u16 = 0xE006;
3771                bytes.extend_from_slice(&b_done.to_le_bytes());
3772
3773                // NOP (2 bytes at offset 16) - padding
3774                bytes.extend_from_slice(&0xBF00u16.to_le_bytes());
3775
3776                // .lo_zero: (offset 18)
3777                // RBIT.W rd, rnhi (4 bytes)
3778                // RBIT T1: hw1 = 0xFA9<Rm>, hw2 = 0xF<Rd>A<Rm>
3779                let hw1: u16 = (0xFA90 | rn_hi_bits) as u16;
3780                let hw2: u16 = (0xF0A0 | (rd_bits << 8) | rn_hi_bits) as u16;
3781                bytes.extend_from_slice(&hw1.to_le_bytes());
3782                bytes.extend_from_slice(&hw2.to_le_bytes());
3783
3784                // CLZ.W rd, rd (4 bytes at offset 22)
3785                // CLZ T1: hw1 = 0xFAB<Rm>, hw2 = 0xF<Rd>8<Rm>
3786                let hw1: u16 = (0xFAB0 | rd_bits) as u16;
3787                let hw2: u16 = (0xF080 | (rd_bits << 8) | rd_bits) as u16;
3788                bytes.extend_from_slice(&hw1.to_le_bytes());
3789                bytes.extend_from_slice(&hw2.to_le_bytes());
3790
3791                // ADD.W rd, rd, #32 (4 bytes at offset 26)
3792                let hw1: u16 = (0xF100 | rd_bits) as u16;
3793                let hw2: u16 = ((rd_bits << 8) | 0x20) as u16;
3794                bytes.extend_from_slice(&hw1.to_le_bytes());
3795                bytes.extend_from_slice(&hw2.to_le_bytes());
3796
3797                // .done: (offset 30)
3798                // i64.ctz returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
3799                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
3800                bytes.extend_from_slice(&mov0.to_le_bytes());
3801
3802                Ok(bytes)
3803            }
3804
3805            // I64Popcnt: Population count of 64-bit value
3806            // result = POPCNT(lo) + POPCNT(hi)
3807            // Using SIMD-style parallel bit counting algorithm
3808            ArmOp::I64Popcnt { rd, rnlo, rnhi } => {
3809                let rd_bits = reg_to_bits(rd);
3810                let rn_lo_bits = reg_to_bits(rnlo);
3811                let rn_hi_bits = reg_to_bits(rnhi);
3812                let r12: u32 = 12; // IP scratch
3813                let r3: u32 = 3; // Scratch for hi popcnt result
3814                let mut bytes = Vec::new();
3815
3816                // PUSH {R3, R4, R5} - save scratch registers
3817                bytes.extend_from_slice(&0xB438u16.to_le_bytes());
3818
3819                // Strategy: compute popcnt(lo) -> R4, popcnt(hi) -> R5, add them -> rd
3820                // Using lookup table approach for each byte would be too large
3821                // Using shift-and-add approach instead
3822
3823                // For simplicity and correctness, use the efficient parallel algorithm
3824                // but implement it as a series of inline operations
3825
3826                // MOV R4, rnlo
3827                let d_bit: u32 = 0; // R4 < 8, so high bit is 0
3828                let mov: u16 = (0x4600 | (d_bit << 7) | (rn_lo_bits << 3) | (4 & 0x7)) as u16;
3829                bytes.extend_from_slice(&mov.to_le_bytes());
3830
3831                // MOV R5, rnhi
3832                let d_bit: u32 = 0; // R5 < 8, so high bit is 0
3833                let mov: u16 = (0x4600 | (d_bit << 7) | (rn_hi_bits << 3) | (5 & 0x7)) as u16;
3834                bytes.extend_from_slice(&mov.to_le_bytes());
3835
3836                // --- POPCNT for R4 (lo word) ---
3837                // Step 1: x = x - ((x >> 1) & 0x55555555)
3838                // LSR.W R12, R4, #1
3839                let hw1: u16 = 0xEA4F;
3840                let hw2: u16 = ((r12 << 8) | 0x50 | 4) as u16;
3841                bytes.extend_from_slice(&hw1.to_le_bytes());
3842                bytes.extend_from_slice(&hw2.to_le_bytes());
3843
3844                // Load 0x55555555 into R3 using MOVW/MOVT
3845                // MOVW R3, #0x5555
3846                bytes.extend_from_slice(&0xF245u16.to_le_bytes());
3847                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3848                // MOVT R3, #0x5555
3849                bytes.extend_from_slice(&0xF2C5u16.to_le_bytes());
3850                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3851
3852                // AND.W R12, R12, R3
3853                let hw1: u16 = (0xEA00 | r12) as u16;
3854                let hw2: u16 = ((r12 << 8) | r3) as u16;
3855                bytes.extend_from_slice(&hw1.to_le_bytes());
3856                bytes.extend_from_slice(&hw2.to_le_bytes());
3857
3858                // SUB.W R4, R4, R12
3859                let hw1: u16 = (0xEBA0 | 4) as u16;
3860                let hw2: u16 = ((4 << 8) | r12) as u16;
3861                bytes.extend_from_slice(&hw1.to_le_bytes());
3862                bytes.extend_from_slice(&hw2.to_le_bytes());
3863
3864                // Step 2: x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
3865                // Load 0x33333333 into R3
3866                // MOVW R3, #0x3333
3867                bytes.extend_from_slice(&0xF243u16.to_le_bytes());
3868                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3869                // MOVT R3, #0x3333
3870                bytes.extend_from_slice(&0xF2C3u16.to_le_bytes());
3871                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3872
3873                // AND.W R12, R4, R3
3874                let hw1: u16 = (0xEA00 | 4) as u16;
3875                let hw2: u16 = ((r12 << 8) | r3) as u16;
3876                bytes.extend_from_slice(&hw1.to_le_bytes());
3877                bytes.extend_from_slice(&hw2.to_le_bytes());
3878
3879                // LSR.W R4, R4, #2
3880                let hw1: u16 = 0xEA4F;
3881                let hw2: u16 = ((4 << 8) | 0x90 | 4) as u16;
3882                bytes.extend_from_slice(&hw1.to_le_bytes());
3883                bytes.extend_from_slice(&hw2.to_le_bytes());
3884
3885                // AND.W R4, R4, R3
3886                let hw1: u16 = (0xEA00 | 4) as u16;
3887                let hw2: u16 = ((4 << 8) | r3) as u16;
3888                bytes.extend_from_slice(&hw1.to_le_bytes());
3889                bytes.extend_from_slice(&hw2.to_le_bytes());
3890
3891                // ADD.W R4, R4, R12
3892                let hw1: u16 = (0xEB00 | 4) as u16;
3893                let hw2: u16 = ((4 << 8) | r12) as u16;
3894                bytes.extend_from_slice(&hw1.to_le_bytes());
3895                bytes.extend_from_slice(&hw2.to_le_bytes());
3896
3897                // Step 3: x = (x + (x >> 4)) & 0x0F0F0F0F
3898                // LSR.W R12, R4, #4
3899                // hw2 = (imm3 << 12) | (Rd << 8) | (imm2 << 6) | (type << 4) | Rm
3900                // imm5=4=00100 → imm3=1, imm2=0, type=01(LSR)
3901                let hw1: u16 = 0xEA4F;
3902                let hw2: u16 = (0x1000 | (r12 << 8) | 0x10 | 4) as u16;
3903                bytes.extend_from_slice(&hw1.to_le_bytes());
3904                bytes.extend_from_slice(&hw2.to_le_bytes());
3905
3906                // ADD.W R4, R4, R12
3907                let hw1: u16 = (0xEB00 | 4) as u16;
3908                let hw2: u16 = ((4 << 8) | r12) as u16;
3909                bytes.extend_from_slice(&hw1.to_le_bytes());
3910                bytes.extend_from_slice(&hw2.to_le_bytes());
3911
3912                // Load 0x0F0F0F0F into R3
3913                // MOVW R3, #0x0F0F (imm4=0, i=1, imm3=7, imm8=0x0F)
3914                // hw1 = 11110 1 10 0100 0000 = 0xF640
3915                // hw2 = 0 111 0011 00001111 = 0x730F
3916                bytes.extend_from_slice(&0xF640u16.to_le_bytes());
3917                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
3918                // MOVT R3, #0x0F0F
3919                bytes.extend_from_slice(&0xF6C0u16.to_le_bytes());
3920                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
3921
3922                // AND.W R4, R4, R3
3923                let hw1: u16 = (0xEA00 | 4) as u16;
3924                let hw2: u16 = ((4 << 8) | r3) as u16;
3925                bytes.extend_from_slice(&hw1.to_le_bytes());
3926                bytes.extend_from_slice(&hw2.to_le_bytes());
3927
3928                // Step 4: x = x * 0x01010101 >> 24
3929                // Load 0x01010101 into R3
3930                // MOVW R3, #0x0101
3931                bytes.extend_from_slice(&0xF240u16.to_le_bytes());
3932                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
3933                // MOVT R3, #0x0101
3934                bytes.extend_from_slice(&0xF2C0u16.to_le_bytes());
3935                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
3936
3937                // MUL R4, R4, R3
3938                // MUL T2: hw1 = 0xFB00|Rn, hw2 = 0xF000|(Rd<<8)|Rm
3939                let hw1: u16 = (0xFB00 | 4) as u16;
3940                let hw2: u16 = (0xF000 | (4 << 8) | r3) as u16;
3941                bytes.extend_from_slice(&hw1.to_le_bytes());
3942                bytes.extend_from_slice(&hw2.to_le_bytes());
3943
3944                // LSR.W R4, R4, #24
3945                // imm5=24=11000 → imm3=6, imm2=0, type=01(LSR)
3946                let hw1: u16 = 0xEA4F;
3947                let hw2: u16 = (0x6000 | (4 << 8) | 0x10 | 4) as u16;
3948                bytes.extend_from_slice(&hw1.to_le_bytes());
3949                bytes.extend_from_slice(&hw2.to_le_bytes());
3950
3951                // --- POPCNT for R5 (hi word) - same algorithm ---
3952                // Step 1
3953                let hw1: u16 = 0xEA4F;
3954                let hw2: u16 = ((r12 << 8) | 0x50 | 5) as u16;
3955                bytes.extend_from_slice(&hw1.to_le_bytes());
3956                bytes.extend_from_slice(&hw2.to_le_bytes());
3957
3958                // Load 0x55555555 into R3
3959                bytes.extend_from_slice(&0xF245u16.to_le_bytes());
3960                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3961                bytes.extend_from_slice(&0xF2C5u16.to_le_bytes());
3962                bytes.extend_from_slice(&0x5355u16.to_le_bytes());
3963
3964                let hw1: u16 = (0xEA00 | r12) as u16;
3965                let hw2: u16 = ((r12 << 8) | r3) as u16;
3966                bytes.extend_from_slice(&hw1.to_le_bytes());
3967                bytes.extend_from_slice(&hw2.to_le_bytes());
3968
3969                let hw1: u16 = (0xEBA0 | 5) as u16;
3970                let hw2: u16 = ((5 << 8) | r12) as u16;
3971                bytes.extend_from_slice(&hw1.to_le_bytes());
3972                bytes.extend_from_slice(&hw2.to_le_bytes());
3973
3974                // Step 2
3975                bytes.extend_from_slice(&0xF243u16.to_le_bytes());
3976                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3977                bytes.extend_from_slice(&0xF2C3u16.to_le_bytes());
3978                bytes.extend_from_slice(&0x3333u16.to_le_bytes());
3979
3980                let hw1: u16 = (0xEA00 | 5) as u16;
3981                let hw2: u16 = ((r12 << 8) | r3) as u16;
3982                bytes.extend_from_slice(&hw1.to_le_bytes());
3983                bytes.extend_from_slice(&hw2.to_le_bytes());
3984
3985                let hw1: u16 = 0xEA4F;
3986                let hw2: u16 = ((5 << 8) | 0x90 | 5) as u16;
3987                bytes.extend_from_slice(&hw1.to_le_bytes());
3988                bytes.extend_from_slice(&hw2.to_le_bytes());
3989
3990                let hw1: u16 = (0xEA00 | 5) as u16;
3991                let hw2: u16 = ((5 << 8) | r3) as u16;
3992                bytes.extend_from_slice(&hw1.to_le_bytes());
3993                bytes.extend_from_slice(&hw2.to_le_bytes());
3994
3995                let hw1: u16 = (0xEB00 | 5) as u16;
3996                let hw2: u16 = ((5 << 8) | r12) as u16;
3997                bytes.extend_from_slice(&hw1.to_le_bytes());
3998                bytes.extend_from_slice(&hw2.to_le_bytes());
3999
4000                // Step 3: LSR.W R12, R5, #4
4001                // imm5=4=00100 → imm3=1, imm2=0, type=01(LSR)
4002                let hw1: u16 = 0xEA4F;
4003                let hw2: u16 = (0x1000 | (r12 << 8) | 0x10 | 5) as u16;
4004                bytes.extend_from_slice(&hw1.to_le_bytes());
4005                bytes.extend_from_slice(&hw2.to_le_bytes());
4006
4007                let hw1: u16 = (0xEB00 | 5) as u16;
4008                let hw2: u16 = ((5 << 8) | r12) as u16;
4009                bytes.extend_from_slice(&hw1.to_le_bytes());
4010                bytes.extend_from_slice(&hw2.to_le_bytes());
4011
4012                // Load 0x0F0F0F0F into R3 (for hi-word)
4013                bytes.extend_from_slice(&0xF640u16.to_le_bytes());
4014                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
4015                bytes.extend_from_slice(&0xF6C0u16.to_le_bytes());
4016                bytes.extend_from_slice(&0x730Fu16.to_le_bytes());
4017
4018                let hw1: u16 = (0xEA00 | 5) as u16;
4019                let hw2: u16 = ((5 << 8) | r3) as u16;
4020                bytes.extend_from_slice(&hw1.to_le_bytes());
4021                bytes.extend_from_slice(&hw2.to_le_bytes());
4022
4023                // Step 4
4024                bytes.extend_from_slice(&0xF240u16.to_le_bytes());
4025                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
4026                bytes.extend_from_slice(&0xF2C0u16.to_le_bytes());
4027                bytes.extend_from_slice(&0x1301u16.to_le_bytes());
4028
4029                // MUL R5, R5, R3
4030                // MUL T2: hw1 = 0xFB00|Rn, hw2 = 0xF000|(Rd<<8)|Rm
4031                let hw1: u16 = (0xFB00 | 5) as u16;
4032                let hw2: u16 = (0xF000 | (5 << 8) | r3) as u16;
4033                bytes.extend_from_slice(&hw1.to_le_bytes());
4034                bytes.extend_from_slice(&hw2.to_le_bytes());
4035
4036                // LSR.W R5, R5, #24
4037                // imm5=24=11000 → imm3=6, imm2=0, type=01(LSR)
4038                let hw1: u16 = 0xEA4F;
4039                let hw2: u16 = (0x6000 | (5 << 8) | 0x10 | 5) as u16;
4040                bytes.extend_from_slice(&hw1.to_le_bytes());
4041                bytes.extend_from_slice(&hw2.to_le_bytes());
4042
4043                // ADD rd, R4, R5 (combine lo and hi counts)
4044                // ADDS Rd, Rn, Rm (T1): 0001 100 Rm Rn Rd = 0x1800 | (Rm<<6) | (Rn<<3) | Rd
4045                let rd_bits_u16 = rd_bits as u16;
4046                let instr: u16 = 0x1800 | (5 << 6) | (4 << 3) | rd_bits_u16;
4047                bytes.extend_from_slice(&instr.to_le_bytes());
4048
4049                // POP {R3, R4, R5}
4050                bytes.extend_from_slice(&0xBC38u16.to_le_bytes());
4051
4052                // i64.popcnt returns i64, so clear high word: MOV rnhi, #0 (2 bytes)
4053                let mov0: u16 = (0x2000 | (rn_hi_bits << 8)) as u16;
4054                bytes.extend_from_slice(&mov0.to_le_bytes());
4055
4056                Ok(bytes)
4057            }
4058
4059            // I64Extend8S: Sign-extend low 8 bits to 64 bits
4060            // Result: rdlo = sign_extend_8(rnlo), rdhi = rdlo >> 31
4061            ArmOp::I64Extend8S { rdlo, rdhi, rnlo } => {
4062                let rdlo_bits = reg_to_bits(rdlo);
4063                let rdhi_bits = reg_to_bits(rdhi);
4064                let rnlo_bits = reg_to_bits(rnlo);
4065                let mut bytes = Vec::new();
4066
4067                // SXTB.W rdlo, rnlo (sign-extend byte to 32-bit)
4068                // SXTB T2: hw1 = 0xFA4F, hw2 = 0xF0<Rd><Rm>
4069                let hw1: u16 = 0xFA4F_u16;
4070                let hw2: u16 = (0xF080 | (rdlo_bits << 8) | rnlo_bits) as u16;
4071                bytes.extend_from_slice(&hw1.to_le_bytes());
4072                bytes.extend_from_slice(&hw2.to_le_bytes());
4073
4074                // ASR.W rdhi, rdlo, #31 (sign-extend to high word)
4075                // ASR (immediate): hw1 = 0xEA4F, hw2 = imm3:Rd:imm2:type:Rm
4076                // For imm5=31: imm3=111, imm2=11, type=10 (ASR)
4077                // hw2 = (7 << 12) | (rdhi << 8) | (3 << 6) | (2 << 4) | rdlo
4078                let hw1: u16 = 0xEA4F;
4079                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rdlo_bits) as u16;
4080                bytes.extend_from_slice(&hw1.to_le_bytes());
4081                bytes.extend_from_slice(&hw2.to_le_bytes());
4082
4083                Ok(bytes)
4084            }
4085
4086            // I64Extend16S: Sign-extend low 16 bits to 64 bits
4087            // Result: rdlo = sign_extend_16(rnlo), rdhi = rdlo >> 31
4088            ArmOp::I64Extend16S { rdlo, rdhi, rnlo } => {
4089                let rdlo_bits = reg_to_bits(rdlo);
4090                let rdhi_bits = reg_to_bits(rdhi);
4091                let rnlo_bits = reg_to_bits(rnlo);
4092                let mut bytes = Vec::new();
4093
4094                // SXTH.W rdlo, rnlo (sign-extend halfword to 32-bit)
4095                // SXTH T2: hw1 = 0xFA0F, hw2 = 0xF0<Rd><Rm>
4096                let hw1: u16 = 0xFA0F_u16;
4097                let hw2: u16 = (0xF080 | (rdlo_bits << 8) | rnlo_bits) as u16;
4098                bytes.extend_from_slice(&hw1.to_le_bytes());
4099                bytes.extend_from_slice(&hw2.to_le_bytes());
4100
4101                // ASR.W rdhi, rdlo, #31 (sign-extend to high word)
4102                let hw1: u16 = 0xEA4F;
4103                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rdlo_bits) as u16;
4104                bytes.extend_from_slice(&hw1.to_le_bytes());
4105                bytes.extend_from_slice(&hw2.to_le_bytes());
4106
4107                Ok(bytes)
4108            }
4109
4110            // I64Extend32S: Sign-extend low 32 bits to 64 bits
4111            // Result: rdlo = rnlo, rdhi = rnlo >> 31
4112            ArmOp::I64Extend32S { rdlo, rdhi, rnlo } => {
4113                let rdlo_bits = reg_to_bits(rdlo);
4114                let rdhi_bits = reg_to_bits(rdhi);
4115                let rnlo_bits = reg_to_bits(rnlo);
4116                let mut bytes = Vec::new();
4117
4118                // MOV rdlo, rnlo (if different)
4119                if rdlo_bits != rnlo_bits {
4120                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4121                    let d_bit = ((rdlo_bits >> 3) & 1) as u16;
4122                    let mov: u16 = 0x4600
4123                        | (d_bit << 7)
4124                        | ((rnlo_bits as u16) << 3)
4125                        | ((rdlo_bits & 0x7) as u16);
4126                    bytes.extend_from_slice(&mov.to_le_bytes());
4127                }
4128
4129                // ASR.W rdhi, rnlo, #31 (sign-extend to high word)
4130                let hw1: u16 = 0xEA4F;
4131                let hw2: u16 = (0x70E0 | (rdhi_bits << 8) | rnlo_bits) as u16;
4132                bytes.extend_from_slice(&hw1.to_le_bytes());
4133                bytes.extend_from_slice(&hw2.to_le_bytes());
4134
4135                Ok(bytes)
4136            }
4137
4138            // SelectMove: IT <cond>; MOV{cond} rd, rm
4139            // Conditional move: only execute MOV if condition is true
4140            ArmOp::SelectMove { rd, rm, cond } => {
4141                let rd_bits = reg_to_bits(rd) as u16;
4142                let rm_bits = reg_to_bits(rm) as u16;
4143
4144                // Condition code encoding for IT block
4145                use synth_synthesis::Condition;
4146                let cond_bits: u16 = match cond {
4147                    Condition::EQ => 0x0, // Equal
4148                    Condition::NE => 0x1, // Not equal
4149                    Condition::HS => 0x2, // Higher or same (unsigned >=)
4150                    Condition::LO => 0x3, // Lower (unsigned <)
4151                    Condition::HI => 0x8, // Higher (unsigned >)
4152                    Condition::LS => 0x9, // Lower or same (unsigned <=)
4153                    Condition::GE => 0xA, // Greater or equal (signed)
4154                    Condition::LT => 0xB, // Less than (signed)
4155                    Condition::GT => 0xC, // Greater than (signed)
4156                    Condition::LE => 0xD, // Less or equal (signed)
4157                };
4158
4159                // IT <cond>: single Then block (mask = 0x8 for T only)
4160                // IT instruction: 1011 1111 firstcond mask
4161                let it_instr: u16 = 0xBF00 | (cond_bits << 4) | 0x8;
4162
4163                // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4164                // This MOV will only execute if condition is true due to IT block
4165                let d_bit = (rd_bits >> 3) & 1;
4166                let mov_instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
4167
4168                // Emit: IT <cond>, MOV rd, rm
4169                let mut bytes = it_instr.to_le_bytes().to_vec();
4170                bytes.extend_from_slice(&mov_instr.to_le_bytes());
4171                Ok(bytes)
4172            }
4173
4174            // Popcnt: Population count (count set bits)
4175            // ARM Cortex-M has no native POPCNT, so we implement the bit manipulation algorithm:
4176            // x = x - ((x >> 1) & 0x55555555);
4177            // x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
4178            // x = (x + (x >> 4)) & 0x0F0F0F0F;
4179            // x = x + (x >> 8);
4180            // x = x + (x >> 16);
4181            // return x & 0x3F;
4182            //
4183            // Uses rd as working register and R12 as scratch for constants
4184            ArmOp::Popcnt { rd, rm } => {
4185                let mut bytes = Vec::new();
4186
4187                // First, move rm to rd if they're different
4188                if rd != rm {
4189                    let rd_bits = reg_to_bits(rd) as u16;
4190                    let rm_bits = reg_to_bits(rm) as u16;
4191                    // MOV Rd, Rm (16-bit): 0100 0110 D Rm Rd[2:0]
4192                    let d_bit = (rd_bits >> 3) & 1;
4193                    let mov_instr: u16 = 0x4600 | (d_bit << 7) | (rm_bits << 3) | (rd_bits & 0x7);
4194                    bytes.extend_from_slice(&mov_instr.to_le_bytes());
4195                }
4196
4197                // Step 1: x = x - ((x >> 1) & 0x55555555)
4198                // Load 0x55555555 into R12
4199                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x5555)?);
4200                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x5555)?);
4201
4202                // R12_temp = rd >> 1
4203                // We need a second scratch register. Use R11.
4204                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 1)?);
4205
4206                // R11 = R11 & R12 (R11 = (x >> 1) & 0x55555555)
4207                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(11, 11, 12)?);
4208
4209                // rd = rd - R11
4210                bytes.extend_from_slice(&self.encode_thumb32_sub_reg_raw(
4211                    reg_to_bits(rd),
4212                    reg_to_bits(rd),
4213                    11,
4214                )?);
4215
4216                // Step 2: x = (x & 0x33333333) + ((x >> 2) & 0x33333333)
4217                // Load 0x33333333 into R12
4218                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x3333)?);
4219                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x3333)?);
4220
4221                // R11 = rd & R12
4222                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4223                    11,
4224                    reg_to_bits(rd),
4225                    12,
4226                )?);
4227
4228                // rd = rd >> 2
4229                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(
4230                    reg_to_bits(rd),
4231                    reg_to_bits(rd),
4232                    2,
4233                )?);
4234
4235                // rd = rd & R12
4236                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4237                    reg_to_bits(rd),
4238                    reg_to_bits(rd),
4239                    12,
4240                )?);
4241
4242                // rd = rd + R11
4243                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4244                    reg_to_bits(rd),
4245                    reg_to_bits(rd),
4246                    11,
4247                )?);
4248
4249                // Step 3: x = (x + (x >> 4)) & 0x0F0F0F0F
4250                // R11 = rd >> 4
4251                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 4)?);
4252
4253                // rd = rd + R11
4254                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4255                    reg_to_bits(rd),
4256                    reg_to_bits(rd),
4257                    11,
4258                )?);
4259
4260                // Load 0x0F0F0F0F into R12
4261                bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, 0x0F0F)?);
4262                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, 0x0F0F)?);
4263
4264                // rd = rd & R12
4265                bytes.extend_from_slice(&self.encode_thumb32_and_reg_raw(
4266                    reg_to_bits(rd),
4267                    reg_to_bits(rd),
4268                    12,
4269                )?);
4270
4271                // Step 4: x = x + (x >> 8)
4272                // R11 = rd >> 8
4273                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 8)?);
4274
4275                // rd = rd + R11
4276                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4277                    reg_to_bits(rd),
4278                    reg_to_bits(rd),
4279                    11,
4280                )?);
4281
4282                // Step 5: x = x + (x >> 16)
4283                // R11 = rd >> 16
4284                bytes.extend_from_slice(&self.encode_thumb32_lsr_raw(11, reg_to_bits(rd), 16)?);
4285
4286                // rd = rd + R11
4287                bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
4288                    reg_to_bits(rd),
4289                    reg_to_bits(rd),
4290                    11,
4291                )?);
4292
4293                // Step 6: return x & 0x3F
4294                // AND with 0x3F (small immediate, can use BIC or AND with immediate)
4295                bytes.extend_from_slice(&self.encode_thumb32_and_imm_raw(
4296                    reg_to_bits(rd),
4297                    reg_to_bits(rd),
4298                    0x3F,
4299                )?);
4300
4301                Ok(bytes)
4302            }
4303
4304            // I64DivU: 64-bit unsigned division using binary long division
4305            // Core: R0:R1 = dividend, R2:R3 = divisor -> R0:R1 = quotient
4306            // Uses: R4-R7, R12 as loop counter (avoid R8 for Renode compatibility)
4307            //
4308            // #610: the fixed-ABI wrapper marshals the selector-assigned
4309            // operand registers into the core's fixed regs and lands the
4310            // result in rd — pre-#610 this arm IGNORED its register fields,
4311            // so the selector read its rd pair (e.g. R4:R5) after the core's
4312            // own POP restored the stale caller values over it: 0 for every
4313            // input. A zero divisor now traps (UDF #0), per WASM semantics.
4314            ArmOp::I64DivU {
4315                rdlo,
4316                rdhi,
4317                rnlo,
4318                rnhi,
4319                rmlo,
4320                rmhi,
4321            } => {
4322                let mut bytes = Vec::new();
4323                emit_i64_fixed_abi_entry(&mut bytes, &[rnlo, rnhi, rmlo, rmhi]);
4324                emit_i64_divisor_zero_trap(&mut bytes);
4325
4326                // PUSH {R4-R7} - save scratch registers (NO LR — this is inline code)
4327                // 16-bit PUSH: 1011 010 M rrrrrrrr where M=0 (no LR), r=R4-R7 = 0xF0
4328                // Encoding: 1011 0100 1111 0000 = 0xB4F0
4329                bytes.extend_from_slice(&0xB4F0u16.to_le_bytes());
4330
4331                // Initialize quotient (R4:R5) = 0
4332                bytes.extend_from_slice(&0x2400u16.to_le_bytes()); // MOV R4, #0
4333                bytes.extend_from_slice(&0x2500u16.to_le_bytes()); // MOV R5, #0
4334
4335                // Initialize remainder (R6:R7) = 0
4336                bytes.extend_from_slice(&0x2600u16.to_le_bytes()); // MOV R6, #0
4337                bytes.extend_from_slice(&0x2700u16.to_le_bytes()); // MOV R7, #0
4338
4339                // Initialize loop counter R12 = 64 (use R12 scratch instead of R8)
4340                // MOV.W R12, #64: F04F 0C40
4341                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4342                bytes.extend_from_slice(&0x0C40u16.to_le_bytes());
4343
4344                // Loop start
4345                let loop_start = bytes.len();
4346
4347                // === Loop body: process one bit ===
4348
4349                // 1. Shift quotient R4:R5 left by 1
4350                // LSLS R5, R5, #1 (16-bit: 0000 0010 1010 1101 = 0x006D -> actually 0x002D for LSL R5,R5,#1)
4351                // LSL Rd, Rm, #imm5: 000 00 imm5 Rm Rd = 000 00 00001 101 101 = 0x006D
4352                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4353                // Get carry from R4 into R5: ORR R5, R5, R4 LSR #31
4354                // Thumb-2 ORR with shifted register: EA45 75D4 = ORR.W R5, R5, R4, LSR #31
4355                // 11101010 010 S Rn | 0 imm3 Rd imm2 type Rm
4356                // type=01 (LSR), imm5=31 (imm3=111, imm2=11)
4357                bytes.extend_from_slice(&0xEA45u16.to_le_bytes());
4358                bytes.extend_from_slice(&0x75D4u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4359                // LSLS R4, R4, #1: 000 00 00001 100 100 = 0x0064
4360                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4361
4362                // 2. Shift remainder R6:R7 left by 1, OR in MSB of dividend R1
4363                // LSLS R7, R7, #1
4364                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4365                // ORR.W R7, R7, R6, LSR #31
4366                bytes.extend_from_slice(&0xEA47u16.to_le_bytes());
4367                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4368                // LSLS R6, R6, #1
4369                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4370                // ORR.W R6, R6, R1, LSR #31 (bring in MSB of dividend high)
4371                bytes.extend_from_slice(&0xEA46u16.to_le_bytes());
4372                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4373
4374                // 3. Shift dividend R0:R1 left by 1
4375                // LSLS R1, R1, #1
4376                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4377                // ORR.W R1, R1, R0, LSR #31
4378                bytes.extend_from_slice(&0xEA41u16.to_le_bytes());
4379                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4380                // LSLS R0, R0, #1
4381                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4382
4383                // 4. Compare remainder >= divisor (64-bit unsigned comparison)
4384                // Compare high words first: CMP R7, R3
4385                // CMP Rn, Rm encoding: 0x4280 | (Rm << 3) | Rn
4386                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3 (16-bit)
4387                // BHI means R7 > R3 (unsigned) - definitely subtract
4388                // BLO means R7 < R3 - definitely don't subtract
4389                // BEQ means need to check low words
4390
4391                // If high > divisor high: branch to subtract (forward +offset)
4392                // BHI.N +6 (skip CMP, skip BLO, do subtract)
4393                // BHI: 1101 1000 offset8 where cond=1000 (HI)
4394                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4 (to subtract block)
4395
4396                // If high < divisor high: branch past subtract
4397                // BLO.N +10 (skip to decrement)
4398                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BLO/BCC +12 (past subtract)
4399
4400                // High words equal, compare low: CMP R6, R2
4401                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2 (16-bit)
4402                // BLO/BCC past subtract (skip SUBS+SBC.W+ORR.W = 10 bytes = 4 halfwords from PC+4)
4403                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords (past subtract)
4404
4405                // === Subtract block: remainder -= divisor, quotient |= 1 ===
4406                // SUBS R6, R6, R2
4407                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2 (16-bit)
4408                // SBC R7, R7, R3 (with borrow)
4409                // Thumb-2 SBC.W: EB67 0703 = SBC.W R7, R7, R3
4410                bytes.extend_from_slice(&0xEB67u16.to_le_bytes());
4411                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4412                // ORR R4, R4, #1 (set bit 0 of quotient low)
4413                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4414                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4415
4416                // === Decrement counter and loop ===
4417                // SUBS.W R12, R12, #1 (decrement loop counter)
4418                // SUBS.W R12, R12, #1: F1BC 0C01
4419                bytes.extend_from_slice(&0xF1BCu16.to_le_bytes());
4420                bytes.extend_from_slice(&0x0C01u16.to_le_bytes());
4421
4422                // BNE back to loop_start
4423                let branch_offset_bytes = bytes.len() - loop_start + 4; // +4 for pipeline
4424                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4425                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4426                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4427
4428                // === Loop done, move quotient to R0:R1 ===
4429                bytes.extend_from_slice(&0x4620u16.to_le_bytes()); // MOV R0, R4
4430                bytes.extend_from_slice(&0x4629u16.to_le_bytes()); // MOV R1, R5
4431
4432                // POP {R4-R7} - restore scratch registers (NO PC — inline code continues)
4433                // 16-bit POP: 1011 110 P rrrrrrrr where P=0 (no PC), r=R4-R7 = 0xF0
4434                // Encoding: 1011 1100 1111 0000 = 0xBCF0
4435                bytes.extend_from_slice(&0xBCF0u16.to_le_bytes());
4436
4437                emit_i64_fixed_abi_exit(&mut bytes, rdlo, rdhi)?;
4438                Ok(bytes)
4439            }
4440
4441            // I64DivS: 64-bit signed division
4442            // Converts to unsigned, divides, then applies sign
4443            // Core: R0:R1 = dividend (signed), R2:R3 = divisor (signed)
4444            //   ->  R0:R1 = quotient (signed)
4445            // #610: fixed-ABI wrapper + zero-divisor trap (see I64DivU).
4446            ArmOp::I64DivS {
4447                rdlo,
4448                rdhi,
4449                rnlo,
4450                rnhi,
4451                rmlo,
4452                rmhi,
4453            } => {
4454                let mut bytes = Vec::new();
4455                emit_i64_fixed_abi_entry(&mut bytes, &[rnlo, rnhi, rmlo, rmhi]);
4456                emit_i64_divisor_zero_trap(&mut bytes);
4457
4458                // PUSH {R4-R11} - save scratch registers (NO LR — inline code)
4459                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4460                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4461
4462                // Save result sign in R9: R9 = R1 XOR R3 (sign bit = MSB)
4463                // EOR.W R9, R1, R3
4464                bytes.extend_from_slice(&0xEA81u16.to_le_bytes());
4465                bytes.extend_from_slice(&0x0903u16.to_le_bytes());
4466
4467                // If dividend negative (R1 MSB set), negate it
4468                // TST R1, R1 (check sign)
4469                bytes.extend_from_slice(&0x4209u16.to_le_bytes()); // TST R1, R1
4470                // BPL skip_neg_dividend (+10 bytes = 5 halfwords)
4471                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4472
4473                // Negate R0:R1 (64-bit): RSBS R0, R0, #0; SBC R1, R1, R1 LSL #1
4474                // Actually: MVN R0, R0; MVN R1, R1; ADDS R0, R0, #1; ADC R1, R1, #0
4475                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4476                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4477                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4478                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4479                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4480
4481                // If divisor negative (R3 MSB set), negate it
4482                bytes.extend_from_slice(&0x421Bu16.to_le_bytes()); // TST R3, R3
4483                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4484
4485                // Negate R2:R3
4486                bytes.extend_from_slice(&0x43D2u16.to_le_bytes()); // MVNS R2, R2
4487                bytes.extend_from_slice(&0x43DBu16.to_le_bytes()); // MVNS R3, R3
4488                bytes.extend_from_slice(&0x1C52u16.to_le_bytes()); // ADDS R2, R2, #1
4489                bytes.extend_from_slice(&0xF143u16.to_le_bytes()); // ADC.W R3, R3, #0
4490                bytes.extend_from_slice(&0x0300u16.to_le_bytes());
4491
4492                // === Now do unsigned division (same as I64DivU) ===
4493                // Initialize quotient (R4:R5) = 0
4494                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4495                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4496                // Initialize remainder (R6:R7) = 0
4497                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4498                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4499                // Initialize loop counter R8 = 64
4500                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4501                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4502
4503                let loop_start = bytes.len();
4504
4505                // Shift quotient left
4506                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4507                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4508                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4509                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4510
4511                // Shift remainder left, OR in MSB of dividend
4512                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4513                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4514                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4515                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4516                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4517                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4518
4519                // Shift dividend left
4520                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4521                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4522                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4523                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4524
4525                // Compare and conditionally subtract
4526                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4527                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4528                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4529                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4530                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4531
4532                // Subtract and set quotient bit
4533                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4534                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4535                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4536                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4537                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4538
4539                // Decrement and loop
4540                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4541                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4542
4543                let branch_offset_bytes = bytes.len() - loop_start + 4;
4544                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4545                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4546                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4547
4548                // Move quotient to R0:R1
4549                bytes.extend_from_slice(&0x4620u16.to_le_bytes()); // MOV R0, R4
4550                bytes.extend_from_slice(&0x4629u16.to_le_bytes()); // MOV R1, R5
4551
4552                // If result should be negative (R9 MSB set), negate R0:R1
4553                bytes.extend_from_slice(&0xF1B9u16.to_le_bytes()); // TST.W R9, R9 (check MSB)
4554                bytes.extend_from_slice(&0x0F00u16.to_le_bytes());
4555                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8 (skip negation)
4556
4557                // Negate result R0:R1
4558                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4559                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4560                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4561                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4562                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4563
4564                // POP {R4-R11} - restore scratch registers (NO PC — inline code continues)
4565                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4566                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4567
4568                emit_i64_fixed_abi_exit(&mut bytes, rdlo, rdhi)?;
4569                Ok(bytes)
4570            }
4571
4572            // I64RemU: 64-bit unsigned remainder using binary long division
4573            // Same algorithm as I64DivU but returns remainder instead of quotient
4574            // Core: R0:R1 = dividend, R2:R3 = divisor -> R0:R1 = remainder
4575            // #610: fixed-ABI wrapper + zero-divisor trap (see I64DivU).
4576            ArmOp::I64RemU {
4577                rdlo,
4578                rdhi,
4579                rnlo,
4580                rnhi,
4581                rmlo,
4582                rmhi,
4583            } => {
4584                let mut bytes = Vec::new();
4585                emit_i64_fixed_abi_entry(&mut bytes, &[rnlo, rnhi, rmlo, rmhi]);
4586                emit_i64_divisor_zero_trap(&mut bytes);
4587
4588                // PUSH {R4-R8} - save scratch registers (NO LR — inline code)
4589                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4590                bytes.extend_from_slice(&0x01F0u16.to_le_bytes());
4591
4592                // Initialize quotient (R4:R5) = 0 (computed but not returned)
4593                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4594                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4595                // Initialize remainder (R6:R7) = 0
4596                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4597                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4598                // Initialize loop counter R8 = 64
4599                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4600                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4601
4602                let loop_start = bytes.len();
4603
4604                // Shift quotient left (not needed for result, but keeps algorithm same)
4605                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4606                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4607                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4608                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4609
4610                // Shift remainder left, OR in MSB of dividend
4611                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4612                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4613                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4614                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4615                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4616                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4617
4618                // Shift dividend left
4619                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4620                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4621                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4622                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4623
4624                // Compare and conditionally subtract
4625                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4626                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4627                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4628                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4629                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4630
4631                // Subtract and set quotient bit
4632                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4633                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4634                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4635                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4636                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4637
4638                // Decrement and loop
4639                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4640                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4641
4642                let branch_offset_bytes = bytes.len() - loop_start + 4;
4643                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4644                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4645                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4646
4647                // Move REMAINDER to R0:R1 (difference from I64DivU)
4648                bytes.extend_from_slice(&0x4630u16.to_le_bytes()); // MOV R0, R6
4649                bytes.extend_from_slice(&0x4639u16.to_le_bytes()); // MOV R1, R7
4650
4651                // POP {R4-R8} - restore scratch registers (NO PC — inline code continues)
4652                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4653                bytes.extend_from_slice(&0x01F0u16.to_le_bytes());
4654
4655                emit_i64_fixed_abi_exit(&mut bytes, rdlo, rdhi)?;
4656                Ok(bytes)
4657            }
4658
4659            // I64RemS: 64-bit signed remainder
4660            // Remainder sign follows dividend sign (not quotient rule)
4661            // Core: R0:R1 = dividend (signed), R2:R3 = divisor (signed)
4662            //   ->  R0:R1 = remainder (signed, same sign as dividend)
4663            // #610: fixed-ABI wrapper + zero-divisor trap (see I64DivU).
4664            ArmOp::I64RemS {
4665                rdlo,
4666                rdhi,
4667                rnlo,
4668                rnhi,
4669                rmlo,
4670                rmhi,
4671            } => {
4672                let mut bytes = Vec::new();
4673                emit_i64_fixed_abi_entry(&mut bytes, &[rnlo, rnhi, rmlo, rmhi]);
4674                emit_i64_divisor_zero_trap(&mut bytes);
4675
4676                // PUSH {R4-R11} - save scratch registers (NO LR — inline code)
4677                bytes.extend_from_slice(&0xE92Du16.to_le_bytes());
4678                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4679
4680                // Save dividend sign in R9 (remainder sign = dividend sign)
4681                // MOV R9, R1 (just need the sign bit)
4682                bytes.extend_from_slice(&0x4689u16.to_le_bytes()); // MOV R9, R1
4683
4684                // If dividend negative (R1 MSB set), negate it
4685                bytes.extend_from_slice(&0x4209u16.to_le_bytes()); // TST R1, R1
4686                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4687
4688                // Negate R0:R1
4689                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4690                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4691                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4692                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4693                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4694
4695                // If divisor negative (R3 MSB set), negate it
4696                bytes.extend_from_slice(&0x421Bu16.to_le_bytes()); // TST R3, R3
4697                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4698
4699                // Negate R2:R3
4700                bytes.extend_from_slice(&0x43D2u16.to_le_bytes()); // MVNS R2, R2
4701                bytes.extend_from_slice(&0x43DBu16.to_le_bytes()); // MVNS R3, R3
4702                bytes.extend_from_slice(&0x1C52u16.to_le_bytes()); // ADDS R2, R2, #1
4703                bytes.extend_from_slice(&0xF143u16.to_le_bytes()); // ADC.W R3, R3, #0
4704                bytes.extend_from_slice(&0x0300u16.to_le_bytes());
4705
4706                // === Unsigned division algorithm ===
4707                // Initialize quotient (R4:R5) = 0
4708                bytes.extend_from_slice(&0x2400u16.to_le_bytes());
4709                bytes.extend_from_slice(&0x2500u16.to_le_bytes());
4710                // Initialize remainder (R6:R7) = 0
4711                bytes.extend_from_slice(&0x2600u16.to_le_bytes());
4712                bytes.extend_from_slice(&0x2700u16.to_le_bytes());
4713                // Initialize loop counter R8 = 64
4714                bytes.extend_from_slice(&0xF04Fu16.to_le_bytes());
4715                bytes.extend_from_slice(&0x0840u16.to_le_bytes());
4716
4717                let loop_start = bytes.len();
4718
4719                // Shift quotient left
4720                bytes.extend_from_slice(&0x006Du16.to_le_bytes()); // LSLS R5, R5, #1
4721                bytes.extend_from_slice(&0xEA45u16.to_le_bytes()); // ORR.W R5, R5, R4, LSR #31
4722                bytes.extend_from_slice(&0x75D4u16.to_le_bytes());
4723                bytes.extend_from_slice(&0x0064u16.to_le_bytes()); // LSLS R4, R4, #1
4724
4725                // Shift remainder left, OR in MSB of dividend
4726                bytes.extend_from_slice(&0x007Fu16.to_le_bytes()); // LSLS R7, R7, #1
4727                bytes.extend_from_slice(&0xEA47u16.to_le_bytes()); // ORR.W R7, R7, R6, LSR #31
4728                bytes.extend_from_slice(&0x77D6u16.to_le_bytes());
4729                bytes.extend_from_slice(&0x0076u16.to_le_bytes()); // LSLS R6, R6, #1
4730                bytes.extend_from_slice(&0xEA46u16.to_le_bytes()); // ORR.W R6, R6, R1, LSR #31
4731                bytes.extend_from_slice(&0x76D1u16.to_le_bytes());
4732
4733                // Shift dividend left
4734                bytes.extend_from_slice(&0x0049u16.to_le_bytes()); // LSLS R1, R1, #1
4735                bytes.extend_from_slice(&0xEA41u16.to_le_bytes()); // ORR.W R1, R1, R0, LSR #31
4736                bytes.extend_from_slice(&0x71D0u16.to_le_bytes());
4737                bytes.extend_from_slice(&0x0040u16.to_le_bytes()); // LSLS R0, R0, #1
4738
4739                // Compare and conditionally subtract
4740                bytes.extend_from_slice(&0x429Fu16.to_le_bytes()); // CMP R7, R3
4741                bytes.extend_from_slice(&0xD802u16.to_le_bytes()); // BHI +4
4742                bytes.extend_from_slice(&0xD306u16.to_le_bytes()); // BCC +12
4743                bytes.extend_from_slice(&0x4296u16.to_le_bytes()); // CMP R6, R2
4744                bytes.extend_from_slice(&0xD304u16.to_le_bytes()); // BCC +4 halfwords
4745
4746                // Subtract and set quotient bit
4747                bytes.extend_from_slice(&0x1AB6u16.to_le_bytes()); // SUBS R6, R6, R2
4748                bytes.extend_from_slice(&0xEB67u16.to_le_bytes()); // SBC.W R7, R7, R3
4749                bytes.extend_from_slice(&0x0703u16.to_le_bytes());
4750                bytes.extend_from_slice(&0xF044u16.to_le_bytes()); // ORR.W R4, R4, #1
4751                bytes.extend_from_slice(&0x0401u16.to_le_bytes());
4752
4753                // Decrement and loop
4754                bytes.extend_from_slice(&0xF1B8u16.to_le_bytes()); // SUB.W R8, R8, #1
4755                bytes.extend_from_slice(&0x0801u16.to_le_bytes());
4756
4757                let branch_offset_bytes = bytes.len() - loop_start + 4;
4758                let offset_halfwords = -((branch_offset_bytes / 2) as i16);
4759                let bne_encoding = 0xD100u16 | ((offset_halfwords as u16) & 0xFF);
4760                bytes.extend_from_slice(&bne_encoding.to_le_bytes());
4761
4762                // Move remainder to R0:R1
4763                bytes.extend_from_slice(&0x4630u16.to_le_bytes()); // MOV R0, R6
4764                bytes.extend_from_slice(&0x4639u16.to_le_bytes()); // MOV R1, R7
4765
4766                // If original dividend was negative (R9 MSB set), negate remainder
4767                bytes.extend_from_slice(&0xF1B9u16.to_le_bytes()); // TST.W R9, R9
4768                bytes.extend_from_slice(&0x0F00u16.to_le_bytes());
4769                bytes.extend_from_slice(&0xD504u16.to_le_bytes()); // BPL +8
4770
4771                // Negate result R0:R1
4772                bytes.extend_from_slice(&0x43C0u16.to_le_bytes()); // MVNS R0, R0
4773                bytes.extend_from_slice(&0x43C9u16.to_le_bytes()); // MVNS R1, R1
4774                bytes.extend_from_slice(&0x1C40u16.to_le_bytes()); // ADDS R0, R0, #1
4775                bytes.extend_from_slice(&0xF141u16.to_le_bytes()); // ADC.W R1, R1, #0
4776                bytes.extend_from_slice(&0x0100u16.to_le_bytes());
4777
4778                // POP {R4-R11} - restore scratch registers (NO PC — inline code continues)
4779                bytes.extend_from_slice(&0xE8BDu16.to_le_bytes());
4780                bytes.extend_from_slice(&0x0FF0u16.to_le_bytes());
4781
4782                emit_i64_fixed_abi_exit(&mut bytes, rdlo, rdhi)?;
4783                Ok(bytes)
4784            }
4785
4786            // === F32 VFP single-precision Thumb-2 encodings ===
4787            // VFP instruction words are identical to ARM32; emit as two LE halfwords.
4788            ArmOp::F32Add { sd, sn, sm } => {
4789                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE300A00, sd, sn, sm)?))
4790            }
4791            ArmOp::F32Sub { sd, sn, sm } => {
4792                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE300A40, sd, sn, sm)?))
4793            }
4794            ArmOp::F32Mul { sd, sn, sm } => {
4795                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE200A00, sd, sn, sm)?))
4796            }
4797            ArmOp::F32Div { sd, sn, sm } => {
4798                Ok(vfp_to_thumb_bytes(encode_vfp_3reg(0xEE800A00, sd, sn, sm)?))
4799            }
4800            ArmOp::F32Abs { sd, sm } => {
4801                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB00AC0, sd, sm)?))
4802            }
4803            ArmOp::F32Neg { sd, sm } => {
4804                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB10A40, sd, sm)?))
4805            }
4806            ArmOp::F32Sqrt { sd, sm } => {
4807                Ok(vfp_to_thumb_bytes(encode_vfp_2reg(0xEEB10AC0, sd, sm)?))
4808            }
4809
4810            // f32 pseudo-ops — multi-instruction sequences
4811            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
4812            ArmOp::F32Ceil { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b01),
4813            ArmOp::F32Floor { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b10),
4814            ArmOp::F32Trunc { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b11),
4815            ArmOp::F32Nearest { sd, sm } => self.encode_thumb_f32_rounding(sd, sm, 0b00),
4816            ArmOp::F32Min { sd, sn, sm } => self.encode_thumb_f32_minmax(sd, sn, sm, true),
4817            ArmOp::F32Max { sd, sn, sm } => self.encode_thumb_f32_minmax(sd, sn, sm, false),
4818            ArmOp::F32Copysign { sd, sn, sm } => self.encode_thumb_f32_copysign(sd, sn, sm),
4819
4820            // f32 comparisons — VCMP + VMRS + MOV #0 + IT + MOV #1
4821            ArmOp::F32Eq { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x0),
4822            ArmOp::F32Ne { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x1),
4823            ArmOp::F32Lt { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x4),
4824            ArmOp::F32Le { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0x9),
4825            ArmOp::F32Gt { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0xC),
4826            ArmOp::F32Ge { rd, sn, sm } => self.encode_thumb_f32_compare(rd, sn, sm, 0xA),
4827
4828            ArmOp::F32Const { sd, value } => self.encode_thumb_f32_const(sd, *value),
4829
4830            ArmOp::F32Load { sd, addr } => {
4831                Ok(vfp_to_thumb_bytes(encode_vfp_ldst(0xED900A00, sd, addr)?))
4832            }
4833            ArmOp::F32Store { sd, addr } => {
4834                Ok(vfp_to_thumb_bytes(encode_vfp_ldst(0xED800A00, sd, addr)?))
4835            }
4836
4837            ArmOp::F32ConvertI32S { sd, rm } => self.encode_thumb_f32_convert_i32(sd, rm, true),
4838            ArmOp::F32ConvertI32U { sd, rm } => self.encode_thumb_f32_convert_i32(sd, rm, false),
4839            ArmOp::F32ConvertI64S { .. } | ArmOp::F32ConvertI64U { .. } => {
4840                Err(synth_core::Error::synthesis(
4841                    "F32 i64 conversion not supported (requires register pairs on 32-bit ARM)",
4842                ))
4843            }
4844            ArmOp::F32ReinterpretI32 { sd, rm } => {
4845                Ok(vfp_to_thumb_bytes(encode_vmov_core_sreg(true, sd, rm)?))
4846            }
4847            ArmOp::I32ReinterpretF32 { rd, sm } => {
4848                Ok(vfp_to_thumb_bytes(encode_vmov_core_sreg(false, sm, rd)?))
4849            }
4850            ArmOp::I32TruncF32S { rd, sm } => self.encode_thumb_i32_trunc_f32(rd, sm, true),
4851            ArmOp::I32TruncF32U { rd, sm } => self.encode_thumb_i32_trunc_f32(rd, sm, false),
4852
4853            // === F64 VFP double-precision Thumb-2 encodings ===
4854            // VFP instruction words are identical to ARM32; emit as two LE halfwords.
4855            ArmOp::F64Add { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4856                0xEE300B00, dd, dn, dm,
4857            )?)),
4858            ArmOp::F64Sub { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4859                0xEE300B40, dd, dn, dm,
4860            )?)),
4861            ArmOp::F64Mul { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4862                0xEE200B00, dd, dn, dm,
4863            )?)),
4864            ArmOp::F64Div { dd, dn, dm } => Ok(vfp_to_thumb_bytes(encode_vfp_3reg_f64(
4865                0xEE800B00, dd, dn, dm,
4866            )?)),
4867            ArmOp::F64Abs { dd, dm } => {
4868                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB00BC0, dd, dm)?))
4869            }
4870            ArmOp::F64Neg { dd, dm } => {
4871                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB10B40, dd, dm)?))
4872            }
4873            ArmOp::F64Sqrt { dd, dm } => {
4874                Ok(vfp_to_thumb_bytes(encode_vfp_2reg_f64(0xEEB10BC0, dd, dm)?))
4875            }
4876
4877            // f64 pseudo-ops
4878            // FPSCR RMode: 00=nearest, 01=+inf(ceil), 10=-inf(floor), 11=zero(trunc)
4879            ArmOp::F64Ceil { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b01),
4880            ArmOp::F64Floor { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b10),
4881            ArmOp::F64Trunc { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b11),
4882            ArmOp::F64Nearest { dd, dm } => self.encode_thumb_f64_rounding(dd, dm, 0b00),
4883            ArmOp::F64Min { dd, dn, dm } => self.encode_thumb_f64_minmax(dd, dn, dm, true),
4884            ArmOp::F64Max { dd, dn, dm } => self.encode_thumb_f64_minmax(dd, dn, dm, false),
4885            ArmOp::F64Copysign { dd, dn, dm } => self.encode_thumb_f64_copysign(dd, dn, dm),
4886
4887            // f64 comparisons
4888            ArmOp::F64Eq { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x0),
4889            ArmOp::F64Ne { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x1),
4890            ArmOp::F64Lt { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x4),
4891            ArmOp::F64Le { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0x9),
4892            ArmOp::F64Gt { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0xC),
4893            ArmOp::F64Ge { rd, dn, dm } => self.encode_thumb_f64_compare(rd, dn, dm, 0xA),
4894
4895            ArmOp::F64Const { dd, value } => self.encode_thumb_f64_const(dd, *value),
4896
4897            ArmOp::F64Load { dd, addr } => Ok(vfp_to_thumb_bytes(encode_vfp_ldst_f64(
4898                0xED900B00, dd, addr,
4899            )?)),
4900            ArmOp::F64Store { dd, addr } => Ok(vfp_to_thumb_bytes(encode_vfp_ldst_f64(
4901                0xED800B00, dd, addr,
4902            )?)),
4903
4904            ArmOp::F64ConvertI32S { dd, rm } => self.encode_thumb_f64_convert_i32(dd, rm, true),
4905            ArmOp::F64ConvertI32U { dd, rm } => self.encode_thumb_f64_convert_i32(dd, rm, false),
4906            ArmOp::F64ConvertI64S { .. } | ArmOp::F64ConvertI64U { .. } => {
4907                Err(synth_core::Error::synthesis(
4908                    "F64 i64 conversion not supported (requires register pairs on 32-bit ARM)",
4909                ))
4910            }
4911            ArmOp::F64PromoteF32 { dd, sm } => self.encode_thumb_f64_promote_f32(dd, sm),
4912            ArmOp::F64ReinterpretI64 { dd, rmlo, rmhi } => Ok(vfp_to_thumb_bytes(
4913                encode_vmov_core_dreg(true, dd, rmlo, rmhi)?,
4914            )),
4915            ArmOp::I64ReinterpretF64 { rdlo, rdhi, dm } => Ok(vfp_to_thumb_bytes(
4916                encode_vmov_core_dreg(false, dm, rdlo, rdhi)?,
4917            )),
4918            ArmOp::I64TruncF64S { .. } | ArmOp::I64TruncF64U { .. } => {
4919                Err(synth_core::Error::synthesis(
4920                    "i64 truncation from F64 not supported (requires i64 register pairs on 32-bit ARM)",
4921                ))
4922            }
4923            ArmOp::I32TruncF64S { rd, dm } => self.encode_thumb_i32_trunc_f64(rd, dm, true),
4924            ArmOp::I32TruncF64U { rd, dm } => self.encode_thumb_i32_trunc_f64(rd, dm, false),
4925
4926            // ===== i64 operations: encode as multi-instruction Thumb-2 sequences =====
4927
4928            // I64Add: ADDS rdlo, rnlo, rmlo; ADC.W rdhi, rnhi, rmhi
4929            ArmOp::I64Add {
4930                rdlo,
4931                rdhi,
4932                rnlo,
4933                rnhi,
4934                rmlo,
4935                rmhi,
4936            } => {
4937                let mut bytes = Vec::new();
4938                // ADDS rdlo, rnlo, rmlo (16-bit)
4939                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Adds {
4940                    rd: *rdlo,
4941                    rn: *rnlo,
4942                    op2: Operand2::Reg(*rmlo),
4943                })?);
4944                // ADC.W rdhi, rnhi, rmhi (32-bit)
4945                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Adc {
4946                    rd: *rdhi,
4947                    rn: *rnhi,
4948                    op2: Operand2::Reg(*rmhi),
4949                })?);
4950                Ok(bytes)
4951            }
4952
4953            // I64Sub: SUBS rdlo, rnlo, rmlo; SBC.W rdhi, rnhi, rmhi
4954            ArmOp::I64Sub {
4955                rdlo,
4956                rdhi,
4957                rnlo,
4958                rnhi,
4959                rmlo,
4960                rmhi,
4961            } => {
4962                let mut bytes = Vec::new();
4963                // SUBS rdlo, rnlo, rmlo (16-bit)
4964                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Subs {
4965                    rd: *rdlo,
4966                    rn: *rnlo,
4967                    op2: Operand2::Reg(*rmlo),
4968                })?);
4969                // SBC.W rdhi, rnhi, rmhi (32-bit)
4970                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Sbc {
4971                    rd: *rdhi,
4972                    rn: *rnhi,
4973                    op2: Operand2::Reg(*rmhi),
4974                })?);
4975                Ok(bytes)
4976            }
4977
4978            // I64And: AND rdlo, rnlo, rmlo; AND rdhi, rnhi, rmhi
4979            ArmOp::I64And {
4980                rdlo,
4981                rdhi,
4982                rnlo,
4983                rnhi,
4984                rmlo,
4985                rmhi,
4986            } => {
4987                let mut bytes = Vec::new();
4988                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::And {
4989                    rd: *rdlo,
4990                    rn: *rnlo,
4991                    op2: Operand2::Reg(*rmlo),
4992                })?);
4993                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::And {
4994                    rd: *rdhi,
4995                    rn: *rnhi,
4996                    op2: Operand2::Reg(*rmhi),
4997                })?);
4998                Ok(bytes)
4999            }
5000
5001            // I64Or: ORR rdlo, rnlo, rmlo; ORR rdhi, rnhi, rmhi
5002            ArmOp::I64Or {
5003                rdlo,
5004                rdhi,
5005                rnlo,
5006                rnhi,
5007                rmlo,
5008                rmhi,
5009            } => {
5010                let mut bytes = Vec::new();
5011                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Orr {
5012                    rd: *rdlo,
5013                    rn: *rnlo,
5014                    op2: Operand2::Reg(*rmlo),
5015                })?);
5016                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Orr {
5017                    rd: *rdhi,
5018                    rn: *rnhi,
5019                    op2: Operand2::Reg(*rmhi),
5020                })?);
5021                Ok(bytes)
5022            }
5023
5024            // I64Xor: EOR rdlo, rnlo, rmlo; EOR rdhi, rnhi, rmhi
5025            ArmOp::I64Xor {
5026                rdlo,
5027                rdhi,
5028                rnlo,
5029                rnhi,
5030                rmlo,
5031                rmhi,
5032            } => {
5033                let mut bytes = Vec::new();
5034                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Eor {
5035                    rd: *rdlo,
5036                    rn: *rnlo,
5037                    op2: Operand2::Reg(*rmlo),
5038                })?);
5039                bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Eor {
5040                    rd: *rdhi,
5041                    rn: *rnhi,
5042                    op2: Operand2::Reg(*rmhi),
5043                })?);
5044                Ok(bytes)
5045            }
5046
5047            // I64Eqz: ORR scratch, lo, hi; ITE EQ; MOV rd, #1; MOV rd, #0
5048            ArmOp::I64Eqz { rd, rnlo, rnhi } => self.encode_thumb(&ArmOp::I64SetCondZ {
5049                rd: *rd,
5050                rn_lo: *rnlo,
5051                rn_hi: *rnhi,
5052            }),
5053
5054            // I64 comparisons: delegate to I64SetCond
5055            ArmOp::I64Eq {
5056                rd,
5057                rnlo,
5058                rnhi,
5059                rmlo,
5060                rmhi,
5061            } => self.encode_thumb(&ArmOp::I64SetCond {
5062                rd: *rd,
5063                rn_lo: *rnlo,
5064                rn_hi: *rnhi,
5065                rm_lo: *rmlo,
5066                rm_hi: *rmhi,
5067                cond: synth_synthesis::Condition::EQ,
5068            }),
5069
5070            ArmOp::I64Ne {
5071                rd,
5072                rnlo,
5073                rnhi,
5074                rmlo,
5075                rmhi,
5076            } => self.encode_thumb(&ArmOp::I64SetCond {
5077                rd: *rd,
5078                rn_lo: *rnlo,
5079                rn_hi: *rnhi,
5080                rm_lo: *rmlo,
5081                rm_hi: *rmhi,
5082                cond: synth_synthesis::Condition::NE,
5083            }),
5084
5085            ArmOp::I64LtS {
5086                rd,
5087                rnlo,
5088                rnhi,
5089                rmlo,
5090                rmhi,
5091            } => self.encode_thumb(&ArmOp::I64SetCond {
5092                rd: *rd,
5093                rn_lo: *rnlo,
5094                rn_hi: *rnhi,
5095                rm_lo: *rmlo,
5096                rm_hi: *rmhi,
5097                cond: synth_synthesis::Condition::LT,
5098            }),
5099
5100            ArmOp::I64LtU {
5101                rd,
5102                rnlo,
5103                rnhi,
5104                rmlo,
5105                rmhi,
5106            } => self.encode_thumb(&ArmOp::I64SetCond {
5107                rd: *rd,
5108                rn_lo: *rnlo,
5109                rn_hi: *rnhi,
5110                rm_lo: *rmlo,
5111                rm_hi: *rmhi,
5112                cond: synth_synthesis::Condition::LO,
5113            }),
5114
5115            ArmOp::I64LeS {
5116                rd,
5117                rnlo,
5118                rnhi,
5119                rmlo,
5120                rmhi,
5121            } => self.encode_thumb(&ArmOp::I64SetCond {
5122                rd: *rd,
5123                rn_lo: *rnlo,
5124                rn_hi: *rnhi,
5125                rm_lo: *rmlo,
5126                rm_hi: *rmhi,
5127                cond: synth_synthesis::Condition::LE,
5128            }),
5129
5130            ArmOp::I64LeU {
5131                rd,
5132                rnlo,
5133                rnhi,
5134                rmlo,
5135                rmhi,
5136            } => self.encode_thumb(&ArmOp::I64SetCond {
5137                rd: *rd,
5138                rn_lo: *rnlo,
5139                rn_hi: *rnhi,
5140                rm_lo: *rmlo,
5141                rm_hi: *rmhi,
5142                cond: synth_synthesis::Condition::LS,
5143            }),
5144
5145            ArmOp::I64GtS {
5146                rd,
5147                rnlo,
5148                rnhi,
5149                rmlo,
5150                rmhi,
5151            } => self.encode_thumb(&ArmOp::I64SetCond {
5152                rd: *rd,
5153                rn_lo: *rnlo,
5154                rn_hi: *rnhi,
5155                rm_lo: *rmlo,
5156                rm_hi: *rmhi,
5157                cond: synth_synthesis::Condition::GT,
5158            }),
5159
5160            ArmOp::I64GtU {
5161                rd,
5162                rnlo,
5163                rnhi,
5164                rmlo,
5165                rmhi,
5166            } => self.encode_thumb(&ArmOp::I64SetCond {
5167                rd: *rd,
5168                rn_lo: *rnlo,
5169                rn_hi: *rnhi,
5170                rm_lo: *rmlo,
5171                rm_hi: *rmhi,
5172                cond: synth_synthesis::Condition::HI,
5173            }),
5174
5175            ArmOp::I64GeS {
5176                rd,
5177                rnlo,
5178                rnhi,
5179                rmlo,
5180                rmhi,
5181            } => self.encode_thumb(&ArmOp::I64SetCond {
5182                rd: *rd,
5183                rn_lo: *rnlo,
5184                rn_hi: *rnhi,
5185                rm_lo: *rmlo,
5186                rm_hi: *rmhi,
5187                cond: synth_synthesis::Condition::GE,
5188            }),
5189
5190            ArmOp::I64GeU {
5191                rd,
5192                rnlo,
5193                rnhi,
5194                rmlo,
5195                rmhi,
5196            } => self.encode_thumb(&ArmOp::I64SetCond {
5197                rd: *rd,
5198                rn_lo: *rnlo,
5199                rn_hi: *rnhi,
5200                rm_lo: *rmlo,
5201                rm_hi: *rmhi,
5202                cond: synth_synthesis::Condition::HS,
5203            }),
5204
5205            // I64Const: MOVW rdlo, lo16; MOVT rdlo, hi16; MOVW rdhi, lo16_hi; MOVT rdhi, hi16_hi
5206            ArmOp::I64Const { rdlo, rdhi, value } => {
5207                let lo32 = *value as u32;
5208                let hi32 = (*value >> 32) as u32;
5209                let mut bytes = Vec::new();
5210                // Load low 32 bits into rdlo
5211                bytes.extend_from_slice(
5212                    &self.encode_thumb32_movw_raw(reg_to_bits(rdlo), lo32 & 0xFFFF)?,
5213                );
5214                if lo32 > 0xFFFF {
5215                    bytes.extend_from_slice(
5216                        &self.encode_thumb32_movt_raw(reg_to_bits(rdlo), lo32 >> 16)?,
5217                    );
5218                }
5219                // Load high 32 bits into rdhi
5220                bytes.extend_from_slice(
5221                    &self.encode_thumb32_movw_raw(reg_to_bits(rdhi), hi32 & 0xFFFF)?,
5222                );
5223                if hi32 > 0xFFFF {
5224                    bytes.extend_from_slice(
5225                        &self.encode_thumb32_movt_raw(reg_to_bits(rdhi), hi32 >> 16)?,
5226                    );
5227                }
5228                Ok(bytes)
5229            }
5230
5231            // I64Ldr: LDR rdlo, [base, offset]; LDR rdhi, [base, offset+4]
5232            ArmOp::I64Ldr { rdlo, rdhi, addr } => {
5233                let mut bytes = Vec::new();
5234                // #372/#382: a memory `i64.load` carries an index register
5235                // (`reg_imm(R11, addr_reg, offset)` = R11 + addr + offset). The
5236                // immediate `encode_thumb32_ldr` below uses only base+offset and
5237                // would SILENTLY DROP `offset_reg` — the #206 defect, here for
5238                // i64. `i64_effective_base` materializes the effective base into
5239                // `ip` (and, when `offset+4 > 0xFFF`, folds the offset in too so
5240                // the function is NOT skipped — #382), returning the residual
5241                // imm12 for the two halves. Frame i64 loads (no `offset_reg`, e.g.
5242                // a spilled local at `[SP, #off]`) keep the plain `[base,#off]`
5243                // form unchanged — so existing output is byte-identical.
5244                let (base, offset) = self.i64_effective_base(&mut bytes, addr)?;
5245                bytes.extend_from_slice(&self.encode_thumb32_ldr(rdlo, &base, offset)?);
5246                bytes.extend_from_slice(&self.encode_thumb32_ldr(
5247                    rdhi,
5248                    &base,
5249                    offset.wrapping_add(4),
5250                )?);
5251                Ok(bytes)
5252            }
5253
5254            // I64Str: STR rdlo, [base, offset]; STR rdhi, [base, offset+4]
5255            ArmOp::I64Str { rdlo, rdhi, addr } => {
5256                let mut bytes = Vec::new();
5257                // #372/#382: same index-materialization + large-offset fold as
5258                // I64Ldr (see above).
5259                let (base, offset) = self.i64_effective_base(&mut bytes, addr)?;
5260                bytes.extend_from_slice(&self.encode_thumb32_str(rdlo, &base, offset)?);
5261                bytes.extend_from_slice(&self.encode_thumb32_str(
5262                    rdhi,
5263                    &base,
5264                    offset.wrapping_add(4),
5265                )?);
5266                Ok(bytes)
5267            }
5268
5269            // I64ExtendI32S: MOV rdlo, rn; ASR rdhi, rdlo, #31 (sign-extend)
5270            ArmOp::I64ExtendI32S { rdlo, rdhi, rn } => {
5271                let mut bytes = Vec::new();
5272                if rdlo != rn {
5273                    // MOV rdlo, rn (16-bit)
5274                    bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Mov {
5275                        rd: *rdlo,
5276                        op2: Operand2::Reg(*rn),
5277                    })?);
5278                }
5279                // ASR rdhi, rdlo, #31 (sign-extend: fill high word with sign bit)
5280                bytes.extend_from_slice(
5281                    &self.encode_thumb32_shift(rdhi, rdlo, 31, 0b10)?, // ASR type
5282                );
5283                Ok(bytes)
5284            }
5285
5286            // I64ExtendI32U: MOV rdlo, rn; MOV rdhi, #0
5287            ArmOp::I64ExtendI32U { rdlo, rdhi, rn } => {
5288                let mut bytes = Vec::new();
5289                if rdlo != rn {
5290                    // MOV rdlo, rn
5291                    bytes.extend_from_slice(&self.encode_thumb(&ArmOp::Mov {
5292                        rd: *rdlo,
5293                        op2: Operand2::Reg(*rn),
5294                    })?);
5295                }
5296                // MOV rdhi, #0 (16-bit: MOVS Rd, #0)
5297                let rdhi_bits = reg_to_bits(rdhi) as u16;
5298                let instr: u16 = 0x2000 | (rdhi_bits << 8);
5299                bytes.extend_from_slice(&instr.to_le_bytes());
5300                Ok(bytes)
5301            }
5302
5303            // I32WrapI64: MOV rd, rnlo (just take low 32 bits)
5304            ArmOp::I32WrapI64 { rd, rnlo } => {
5305                if rd == rnlo {
5306                    // No-op: already in the right register
5307                    let instr: u16 = 0xBF00; // NOP
5308                    Ok(instr.to_le_bytes().to_vec())
5309                } else {
5310                    // MOV rd, rnlo
5311                    self.encode_thumb(&ArmOp::Mov {
5312                        rd: *rd,
5313                        op2: Operand2::Reg(*rnlo),
5314                    })
5315                }
5316            }
5317
5318            // ===== Helium MVE operations (Thumb-2 encoding) =====
5319            ArmOp::MveLoad { qd, addr } => Ok(vfp_to_thumb_bytes(encode_mve_vldrw(qd, addr))),
5320            ArmOp::MveStore { qd, addr } => Ok(vfp_to_thumb_bytes(encode_mve_vstrw(qd, addr))),
5321            ArmOp::MveConst { qd, bytes } => self.encode_thumb_mve_const(qd, bytes),
5322            ArmOp::MveAnd { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5323                0xEF000150, qd, qn, qm,
5324            ))),
5325            ArmOp::MveOrr { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5326                0xEF200150, qd, qn, qm,
5327            ))),
5328            ArmOp::MveEor { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5329                0xFF000150, qd, qn, qm,
5330            ))),
5331            ArmOp::MveMvn { qd, qm } => {
5332                // VMVN Qd, Qm: 0xFFB005C0 | Qd<<12 | Qm
5333                let qd_enc = qreg_to_num(qd);
5334                let qm_enc = qreg_to_num(qm);
5335                let instr: u32 = 0xFFB005C0 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5336                Ok(vfp_to_thumb_bytes(instr))
5337            }
5338            ArmOp::MveBic { qd, qn, qm } => Ok(vfp_to_thumb_bytes(encode_mve_3reg_bitwise(
5339                0xEF100150, qd, qn, qm,
5340            ))),
5341            ArmOp::MveAddI { qd, qn, qm, size } => {
5342                let sz = mve_size_bits(size);
5343                let base: u32 = 0xEF000840 | (sz << 20);
5344                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5345            }
5346            ArmOp::MveSubI { qd, qn, qm, size } => {
5347                let sz = mve_size_bits(size);
5348                let base: u32 = 0xFF000840 | (sz << 20);
5349                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5350            }
5351            ArmOp::MveMulI { qd, qn, qm, size } => {
5352                let sz = mve_size_bits(size);
5353                let base: u32 = 0xEF000950 | (sz << 20);
5354                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5355            }
5356            ArmOp::MveNegI { qd, qm, size } => {
5357                let sz = mve_size_bits(size);
5358                // VNEG.Sx Qd, Qm
5359                let qd_enc = qreg_to_num(qd);
5360                let qm_enc = qreg_to_num(qm);
5361                let base: u32 = 0xFFB103C0 | (sz << 18);
5362                let instr = base | ((qd_enc * 2) << 12) | (qm_enc * 2);
5363                Ok(vfp_to_thumb_bytes(instr))
5364            }
5365            ArmOp::MveDup { qd, rn, size } => {
5366                let sz = mve_size_bits(size);
5367                let qd_enc = qreg_to_num(qd);
5368                let rn_bits = reg_to_bits(rn);
5369                // VDUP.sz Qd, Rn: EEA0 0B10 variant
5370                // size encoding: 00=32, 01=16, 10=8
5371                let be = match sz {
5372                    0 => 0b00u32, // 8-bit
5373                    1 => 0b01,    // 16-bit
5374                    _ => 0b00,    // 32-bit (default)
5375                };
5376                let instr: u32 = 0xEEA00B10 | ((qd_enc * 2) << 16) | (rn_bits << 12) | (be << 5);
5377                Ok(vfp_to_thumb_bytes(instr))
5378            }
5379            ArmOp::MveExtractLane { rd, qn, lane, size } => {
5380                let qn_enc = qreg_to_num(qn);
5381                let rd_bits = reg_to_bits(rd);
5382                // VMOV.sz Rd, Dn[x] — extract from Q-register lane
5383                // For 32-bit: VMOV Rd, Dn — where Dn is the appropriate D-register
5384                let d_reg = qn_enc * 2 + ((*lane as u32) >> 1);
5385                let lane_in_d = (*lane as u32) & 1;
5386                let _sz = mve_size_bits(size);
5387                // VMOV Rd, Dn[x]: EE10 0B10 for 32-bit
5388                let instr: u32 = 0xEE100B10 | (d_reg << 16) | (rd_bits << 12) | (lane_in_d << 21);
5389                Ok(vfp_to_thumb_bytes(instr))
5390            }
5391            ArmOp::MveInsertLane { qd, rn, lane, size } => {
5392                let qd_enc = qreg_to_num(qd);
5393                let rn_bits = reg_to_bits(rn);
5394                let d_reg = qd_enc * 2 + ((*lane as u32) >> 1);
5395                let lane_in_d = (*lane as u32) & 1;
5396                let _sz = mve_size_bits(size);
5397                // VMOV Dn[x], Rn: EE00 0B10 for 32-bit
5398                let instr: u32 = 0xEE000B10 | (d_reg << 16) | (rn_bits << 12) | (lane_in_d << 21);
5399                Ok(vfp_to_thumb_bytes(instr))
5400            }
5401
5402            // MVE float comparisons — emit VCMP + VPSEL sequence (simplified: just VCMP)
5403            ArmOp::MveCmpEqI { qd, qn, qm, size }
5404            | ArmOp::MveCmpNeI { qd, qn, qm, size }
5405            | ArmOp::MveCmpLtS { qd, qn, qm, size }
5406            | ArmOp::MveCmpLtU { qd, qn, qm, size }
5407            | ArmOp::MveCmpGtS { qd, qn, qm, size }
5408            | ArmOp::MveCmpGtU { qd, qn, qm, size }
5409            | ArmOp::MveCmpLeS { qd, qn, qm, size }
5410            | ArmOp::MveCmpLeU { qd, qn, qm, size }
5411            | ArmOp::MveCmpGeS { qd, qn, qm, size }
5412            | ArmOp::MveCmpGeU { qd, qn, qm, size } => {
5413                // Encode as VADD (placeholder encoding — real implementation
5414                // would use VCMP + VPSEL pair)
5415                let sz = mve_size_bits(size);
5416                let base: u32 = 0xEF000840 | (sz << 20);
5417                Ok(vfp_to_thumb_bytes(encode_mve_3reg(base, qd, qn, qm)))
5418            }
5419
5420            // f32x4 MVE arithmetic
5421            ArmOp::MveAddF32 { qd, qn, qm } => {
5422                // VADD.F32 Qd, Qn, Qm (MVE): 0xEF000D40
5423                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF000D40, qd, qn, qm)))
5424            }
5425            ArmOp::MveSubF32 { qd, qn, qm } => {
5426                // VSUB.F32 Qd, Qn, Qm (MVE): 0xEF200D40
5427                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF200D40, qd, qn, qm)))
5428            }
5429            ArmOp::MveMulF32 { qd, qn, qm } => {
5430                // VMUL.F32 Qd, Qn, Qm (MVE): 0xFF000D50
5431                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xFF000D50, qd, qn, qm)))
5432            }
5433            ArmOp::MveNegF32 { qd, qm } => {
5434                let qd_enc = qreg_to_num(qd);
5435                let qm_enc = qreg_to_num(qm);
5436                // VNEG.F32 Qd, Qm: FFB907C0
5437                let instr: u32 = 0xFFB907C0 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5438                Ok(vfp_to_thumb_bytes(instr))
5439            }
5440            ArmOp::MveAbsF32 { qd, qm } => {
5441                let qd_enc = qreg_to_num(qd);
5442                let qm_enc = qreg_to_num(qm);
5443                // VABS.F32 Qd, Qm: FFB90740
5444                let instr: u32 = 0xFFB90740 | ((qd_enc * 2) << 12) | (qm_enc * 2);
5445                Ok(vfp_to_thumb_bytes(instr))
5446            }
5447            ArmOp::MveCmpEqF32 { qd, qn, qm }
5448            | ArmOp::MveCmpNeF32 { qd, qn, qm }
5449            | ArmOp::MveCmpLtF32 { qd, qn, qm }
5450            | ArmOp::MveCmpLeF32 { qd, qn, qm }
5451            | ArmOp::MveCmpGtF32 { qd, qn, qm }
5452            | ArmOp::MveCmpGeF32 { qd, qn, qm } => {
5453                // Placeholder: encode as VADD.F32 (real impl needs VCMP.F32 + VPSEL)
5454                Ok(vfp_to_thumb_bytes(encode_mve_3reg(0xEF000D40, qd, qn, qm)))
5455            }
5456            ArmOp::MveDupF32 { qd, rn } => {
5457                let qd_enc = qreg_to_num(qd);
5458                let rn_bits = reg_to_bits(rn);
5459                // VDUP.32 Qd, Rn (same encoding as integer VDUP.32)
5460                let instr: u32 = 0xEEA00B10 | ((qd_enc * 2) << 16) | (rn_bits << 12);
5461                Ok(vfp_to_thumb_bytes(instr))
5462            }
5463            ArmOp::MveExtractLaneF32 { rd, qn, lane } => {
5464                let qn_enc = qreg_to_num(qn);
5465                let rd_bits = reg_to_bits(rd);
5466                // VMOV Rd, Sn where Sn = Q*4 + lane
5467                let s_num = qn_enc * 4 + (*lane as u32);
5468                let (vn, n) = encode_sreg(s_num);
5469                let instr: u32 = 0xEE100A10 | (vn << 16) | (rd_bits << 12) | (n << 7);
5470                Ok(vfp_to_thumb_bytes(instr))
5471            }
5472            ArmOp::MveReplaceLaneF32 { qd, rn, lane } => {
5473                let qd_enc = qreg_to_num(qd);
5474                let rn_bits = reg_to_bits(rn);
5475                // VMOV Sn, Rn where Sn = Q*4 + lane
5476                let s_num = qd_enc * 4 + (*lane as u32);
5477                let (vn, n) = encode_sreg(s_num);
5478                let instr: u32 = 0xEE000A10 | (vn << 16) | (rn_bits << 12) | (n << 7);
5479                Ok(vfp_to_thumb_bytes(instr))
5480            }
5481            ArmOp::MveDivF32 { qd, qn, qm } => {
5482                // Lane-wise: extract 4 S-regs, VDIV, insert back
5483                self.encode_thumb_mve_lane_wise_f32_binop(qd, qn, qm, 0xEE800A00)
5484            }
5485            ArmOp::MveSqrtF32 { qd, qm } => {
5486                // Lane-wise: extract 4 S-regs, VSQRT, insert back
5487                self.encode_thumb_mve_lane_wise_f32_sqrt(qd, qm)
5488            }
5489
5490            // Catch-all for any remaining ops
5491            _ => {
5492                let instr: u16 = 0xBF00; // NOP
5493                Ok(instr.to_le_bytes().to_vec())
5494            }
5495        }
5496    }
5497
5498    // === Thumb-2 VFP multi-instruction helpers ===
5499
5500    /// Encode F32 comparison as Thumb-2: VCMP.F32 + VMRS + MOVS rd,#0 + IT + MOV rd,#1
5501    fn encode_thumb_f32_compare(
5502        &self,
5503        rd: &Reg,
5504        sn: &VfpReg,
5505        sm: &VfpReg,
5506        cond_code: u32,
5507    ) -> Result<Vec<u8>> {
5508        let mut bytes = Vec::new();
5509        let rd_bits = reg_to_bits(rd);
5510
5511        // VCMP.F32 Sn, Sm
5512        let sn_num = vfp_sreg_to_num(sn)?;
5513        let sm_num = vfp_sreg_to_num(sm)?;
5514        let (vd, d) = encode_sreg(sn_num);
5515        let (vm, m) = encode_sreg(sm_num);
5516        let vcmp = 0xEEB40A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5517        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5518
5519        // VMRS APSR_nzcv, FPSCR: 0xEEF1FA10
5520        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5521
5522        // MOVS Rd, #0 (16-bit): 0010 0 Rd(3) 0000 0000
5523        if rd_bits < 8 {
5524            let movs_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
5525            bytes.extend_from_slice(&movs_zero.to_le_bytes());
5526        } else {
5527            // MOV.W Rd, #0 (32-bit Thumb-2)
5528            let hw1: u16 = 0xF04F;
5529            let hw2: u16 = (rd_bits as u16) << 8;
5530            bytes.extend_from_slice(&hw1.to_le_bytes());
5531            bytes.extend_from_slice(&hw2.to_le_bytes());
5532        }
5533
5534        // IT<cond> — If-Then for conditional MOV
5535        // IT encoding: 1011 1111 cond(4) mask(4)
5536        // mask = 0x8 for single "then" (IT)
5537        let it: u16 = 0xBF00 | ((cond_code as u16) << 4) | 0x8;
5538        bytes.extend_from_slice(&it.to_le_bytes());
5539
5540        // MOV Rd, #1 (16-bit, conditional due to IT): 0010 0 Rd(3) 0000 0001
5541        if rd_bits < 8 {
5542            let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
5543            bytes.extend_from_slice(&mov_one.to_le_bytes());
5544        } else {
5545            // MOV.W Rd, #1 (32-bit)
5546            let hw1: u16 = 0xF04F;
5547            let hw2: u16 = ((rd_bits as u16) << 8) | 0x01;
5548            bytes.extend_from_slice(&hw1.to_le_bytes());
5549            bytes.extend_from_slice(&hw2.to_le_bytes());
5550        }
5551
5552        Ok(bytes)
5553    }
5554
5555    /// Encode F32 constant load as Thumb-2: MOVW + MOVT + VMOV
5556    fn encode_thumb_f32_const(&self, sd: &VfpReg, value: f32) -> Result<Vec<u8>> {
5557        let mut bytes = Vec::new();
5558        let bits = value.to_bits();
5559        let rt: u32 = 12; // R12/IP as temp
5560
5561        // MOVW R12, #lo16
5562        // Thumb-2 MOVW: 11110 i 10 0100 imm4 | 0 imm3 Rd imm8
5563        let lo16 = bits & 0xFFFF;
5564        let imm4 = (lo16 >> 12) & 0xF;
5565        let i_bit = (lo16 >> 11) & 1;
5566        let imm3 = (lo16 >> 8) & 0x7;
5567        let imm8 = lo16 & 0xFF;
5568        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
5569        let hw2: u16 = ((imm3 << 12) | (rt << 8) | imm8) as u16;
5570        bytes.extend_from_slice(&hw1.to_le_bytes());
5571        bytes.extend_from_slice(&hw2.to_le_bytes());
5572
5573        // MOVT R12, #hi16
5574        let hi16 = (bits >> 16) & 0xFFFF;
5575        let imm4 = (hi16 >> 12) & 0xF;
5576        let i_bit = (hi16 >> 11) & 1;
5577        let imm3 = (hi16 >> 8) & 0x7;
5578        let imm8 = hi16 & 0xFF;
5579        let hw1: u16 = (0xF2C0 | (i_bit << 10) | imm4) as u16;
5580        let hw2: u16 = ((imm3 << 12) | (rt << 8) | imm8) as u16;
5581        bytes.extend_from_slice(&hw1.to_le_bytes());
5582        bytes.extend_from_slice(&hw2.to_le_bytes());
5583
5584        // VMOV Sd, R12
5585        let vmov = encode_vmov_core_sreg(true, sd, &Reg::R12)?;
5586        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5587
5588        Ok(bytes)
5589    }
5590
5591    /// Encode VMOV + VCVT.F32.xS32 as Thumb-2
5592    fn encode_thumb_f32_convert_i32(&self, sd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
5593        let mut bytes = Vec::new();
5594
5595        // VMOV Sd, Rm
5596        let vmov = encode_vmov_core_sreg(true, sd, rm)?;
5597        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5598
5599        // VCVT.F32.S32/U32 Sd, Sd
5600        let sd_num = vfp_sreg_to_num(sd)?;
5601        let (vd, d) = encode_sreg(sd_num);
5602        let (vm, m) = encode_sreg(sd_num);
5603        let base = if signed { 0xEEB80A40 } else { 0xEEB80AC0 };
5604        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
5605        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
5606
5607        Ok(bytes)
5608    }
5609
5610    /// Encode F32 rounding pseudo-op as Thumb-2 via VCVT to integer and back
5611    /// Encode F32 rounding as Thumb-2.
5612    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
5613    ///
5614    /// For trunc: uses VCVTR.S32.F32 (always truncates).
5615    /// For ceil/floor/nearest: sets FPSCR rounding mode, uses VCVT.S32.F32 (non-R variant),
5616    /// then restores FPSCR.
5617    fn encode_thumb_f32_rounding(&self, sd: &VfpReg, sm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
5618        let mut bytes = Vec::new();
5619        let sm_num = vfp_sreg_to_num(sm)?;
5620        let sd_num = vfp_sreg_to_num(sd)?;
5621        let (vd_s, d_s) = encode_sreg(sd_num);
5622        let (vm_s, m_s) = encode_sreg(sm_num);
5623
5624        if mode == 0b11 {
5625            // Trunc (toward zero): VCVTR.S32.F32 — bit[7]=1, always truncates
5626            let vcvt_to_int = 0xEEBD0AC0 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
5627            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5628        } else {
5629            // ceil/floor/nearest: manipulate FPSCR rounding mode
5630            let rt: u32 = 12; // R12/IP as temp
5631
5632            // VMRS R12, FPSCR
5633            let vmrs = 0xEEF10A10 | (rt << 12);
5634            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5635
5636            // BIC.W R12, R12, #(3 << 22) — clear RMode bits [23:22]
5637            // Thumb-2 modified immediate for 3<<22 = 0x00C00000:
5638            // BIC.W encoding: 11110 i 0 0001 S Rn | 0 imm3 Rd imm8
5639            // 0x00C00000 = 0x03 shifted left by 22 => Thumb mod-imm: i=0, imm3=0b101, imm8=0x03
5640            let bic_hw1: u16 = 0xF020 | ((rt as u16) & 0xF); // BIC, Rn=R12
5641            let bic_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | 0x03;
5642            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5643            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5644
5645            // ORR.W R12, R12, #(mode << 22)
5646            if mode != 0 {
5647                let orr_hw1: u16 = 0xF040 | ((rt as u16) & 0xF); // ORR, Rn=R12
5648                let orr_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | (mode as u16);
5649                bytes.extend_from_slice(&orr_hw1.to_le_bytes());
5650                bytes.extend_from_slice(&orr_hw2.to_le_bytes());
5651            }
5652
5653            // VMSR FPSCR, R12
5654            let vmsr = 0xEEE10A10 | (rt << 12);
5655            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5656
5657            // VCVT.S32.F32 Sd, Sm — non-R variant (bit[7]=0), uses FPSCR rmode
5658            let vcvt_to_int = 0xEEBD0A40 | (d_s << 22) | (vd_s << 12) | (m_s << 5) | vm_s;
5659            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5660
5661            // Restore FPSCR: clear rmode bits back to nearest (default)
5662            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5663            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5664            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5665            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5666        }
5667
5668        // VCVT.F32.S32 Sd, Sd (convert integer result back to float)
5669        let (vd2, d2) = encode_sreg(sd_num);
5670        let vcvt_to_float = 0xEEB80A40 | (d2 << 22) | (vd2 << 12) | (d_s << 5) | vd_s;
5671        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_float));
5672
5673        Ok(bytes)
5674    }
5675
5676    /// Encode F32 min/max as Thumb-2: VMOV + VCMP + VMRS + IT + VMOV
5677    fn encode_thumb_f32_minmax(
5678        &self,
5679        sd: &VfpReg,
5680        sn: &VfpReg,
5681        sm: &VfpReg,
5682        is_min: bool,
5683    ) -> Result<Vec<u8>> {
5684        let mut bytes = Vec::new();
5685        let sn_num = vfp_sreg_to_num(sn)?;
5686        let sm_num = vfp_sreg_to_num(sm)?;
5687        let sd_num = vfp_sreg_to_num(sd)?;
5688
5689        // VMOV.F32 Sd, Sn
5690        let (vd, d) = encode_sreg(sd_num);
5691        let (vn, n) = encode_sreg(sn_num);
5692        let vmov_sn = 0xEEB00A40 | (d << 22) | (vd << 12) | (n << 5) | vn;
5693        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_sn));
5694
5695        // VCMP.F32 Sn, Sm
5696        let (vm, m) = encode_sreg(sm_num);
5697        let vcmp = 0xEEB40A40 | (n << 22) | (vn << 12) | (m << 5) | vm;
5698        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5699
5700        // VMRS APSR_nzcv, FPSCR
5701        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5702
5703        // IT GT (for min) or IT MI (for max)
5704        let cond: u16 = if is_min { 0xC } else { 0x4 };
5705        let it: u16 = 0xBF00 | (cond << 4) | 0x8;
5706        bytes.extend_from_slice(&it.to_le_bytes());
5707
5708        // VMOV{cond}.F32 Sd, Sm — conditional VMOV in IT block
5709        let vmov_sm = 0xEEB00A40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5710        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_sm));
5711
5712        Ok(bytes)
5713    }
5714
5715    /// Encode F32 copysign as Thumb-2
5716    fn encode_thumb_f32_copysign(&self, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
5717        let mut bytes = Vec::new();
5718
5719        // VMOV R12, Sm (get sign source bits)
5720        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5721            false,
5722            sm,
5723            &Reg::R12,
5724        )?));
5725
5726        // VMOV R0, Sn (get magnitude source bits)
5727        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5728            false,
5729            sn,
5730            &Reg::R0,
5731        )?));
5732
5733        // AND.W R12, R12, #0x80000000
5734        // Thumb-2 modified immediate: 0x80000000 = constant 0x80 with rotation
5735        // Using T1 encoding: 11110 i 0 0000 S Rn | 0 imm3 Rd imm8
5736        // 0x80000000: i=0, imm3=0b001, imm8=0x00 (rotation=4, value=0x80)
5737        // Actually encoding #0x80000000 as modified constant:
5738        // bit pattern 1 followed by 31 zeros: enc = 0b0100_00000000 = 0x0100? No.
5739        // ARM modified immediate: abcdefgh rotated. 0x80000000 = 0x80 ROR 2 = enc 0x0102
5740        // Actually: value = abcdefgh ROR (2*rot). 0x80 = 10000000, ROR 2 gives 0x20000000.
5741        // For 0x80000000: 0x02 ROR 2 = 0x80000000. So imm12 = (1<<8) | 0x02 = 0x102
5742        let hw1: u16 = 0xF000 | 12; // AND.W R12, R12, #modified_const (i=0, Rn=R12)
5743        let hw2: u16 = (0x1 << 12) | (12 << 8) | 0x02; // imm3=1, Rd=R12, imm8=0x02
5744        bytes.extend_from_slice(&hw1.to_le_bytes());
5745        bytes.extend_from_slice(&hw2.to_le_bytes());
5746
5747        // BIC.W R0, R0, #0x80000000 (R0 = register 0, fields are zero)
5748        let hw1: u16 = 0xF020; // BIC.W R0, R0, #modified_const (i=0, Rn=R0)
5749        let hw2: u16 = (0x1 << 12) | 0x02; // imm3=1, Rd=R0, imm8=0x02
5750        bytes.extend_from_slice(&hw1.to_le_bytes());
5751        bytes.extend_from_slice(&hw2.to_le_bytes());
5752
5753        // ORR.W R0, R0, R12 (R0 = register 0)
5754        let hw1: u16 = 0xEA40; // ORR.W R0, R0, R12 (Rn=R0)
5755        let hw2: u16 = 12; // Rd=R0, Rm=R12
5756        bytes.extend_from_slice(&hw1.to_le_bytes());
5757        bytes.extend_from_slice(&hw2.to_le_bytes());
5758
5759        // VMOV Sd, R0
5760        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_sreg(
5761            true,
5762            sd,
5763            &Reg::R0,
5764        )?));
5765
5766        Ok(bytes)
5767    }
5768
5769    /// Encode F64 comparison as Thumb-2: VCMP.F64 + VMRS + MOV #0 + IT + MOV #1
5770    fn encode_thumb_f64_compare(
5771        &self,
5772        rd: &Reg,
5773        dn: &VfpReg,
5774        dm: &VfpReg,
5775        cond_code: u32,
5776    ) -> Result<Vec<u8>> {
5777        let mut bytes = Vec::new();
5778        let rd_bits = reg_to_bits(rd);
5779
5780        // VCMP.F64 Dn, Dm
5781        let dn_num = vfp_dreg_to_num(dn)?;
5782        let dm_num = vfp_dreg_to_num(dm)?;
5783        let (vd, d) = encode_dreg(dn_num);
5784        let (vm, m) = encode_dreg(dm_num);
5785        let vcmp = 0xEEB40B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5786        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5787
5788        // VMRS APSR_nzcv, FPSCR
5789        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5790
5791        // MOVS Rd, #0
5792        if rd_bits < 8 {
5793            let movs_zero: u16 = 0x2000 | ((rd_bits as u16) << 8);
5794            bytes.extend_from_slice(&movs_zero.to_le_bytes());
5795        } else {
5796            let hw1: u16 = 0xF04F;
5797            let hw2: u16 = (rd_bits as u16) << 8;
5798            bytes.extend_from_slice(&hw1.to_le_bytes());
5799            bytes.extend_from_slice(&hw2.to_le_bytes());
5800        }
5801
5802        // IT<cond>
5803        let it: u16 = 0xBF00 | ((cond_code as u16) << 4) | 0x8;
5804        bytes.extend_from_slice(&it.to_le_bytes());
5805
5806        // MOV Rd, #1
5807        if rd_bits < 8 {
5808            let mov_one: u16 = 0x2001 | ((rd_bits as u16) << 8);
5809            bytes.extend_from_slice(&mov_one.to_le_bytes());
5810        } else {
5811            let hw1: u16 = 0xF04F;
5812            let hw2: u16 = ((rd_bits as u16) << 8) | 0x01;
5813            bytes.extend_from_slice(&hw1.to_le_bytes());
5814            bytes.extend_from_slice(&hw2.to_le_bytes());
5815        }
5816
5817        Ok(bytes)
5818    }
5819
5820    /// Encode F64 constant load as Thumb-2: MOVW+MOVT (lo32 into R0) + MOVW+MOVT (hi32 into R12) + VMOV Dd, R0, R12
5821    fn encode_thumb_f64_const(&self, dd: &VfpReg, value: f64) -> Result<Vec<u8>> {
5822        let mut bytes = Vec::new();
5823        let bits = value.to_bits();
5824        let lo32 = bits as u32;
5825        let hi32 = (bits >> 32) as u32;
5826
5827        // MOVW R0, #lo16(lo32)
5828        let lo16 = lo32 & 0xFFFF;
5829        bytes.extend_from_slice(&self.encode_thumb32_movw_raw(0, lo16)?);
5830
5831        // MOVT R0, #hi16(lo32)
5832        let hi16 = (lo32 >> 16) & 0xFFFF;
5833        bytes.extend_from_slice(&self.encode_thumb32_movt_raw(0, hi16)?);
5834
5835        // MOVW R12, #lo16(hi32)
5836        let lo16 = hi32 & 0xFFFF;
5837        bytes.extend_from_slice(&self.encode_thumb32_movw_raw(12, lo16)?);
5838
5839        // MOVT R12, #hi16(hi32)
5840        let hi16 = (hi32 >> 16) & 0xFFFF;
5841        bytes.extend_from_slice(&self.encode_thumb32_movt_raw(12, hi16)?);
5842
5843        // VMOV Dd, R0, R12
5844        let vmov = encode_vmov_core_dreg(true, dd, &Reg::R0, &Reg::R12)?;
5845        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5846
5847        Ok(bytes)
5848    }
5849
5850    /// Encode VMOV Sd, Rm + VCVT.F64.S32/U32 Dd, Sd as Thumb-2
5851    fn encode_thumb_f64_convert_i32(&self, dd: &VfpReg, rm: &Reg, signed: bool) -> Result<Vec<u8>> {
5852        let mut bytes = Vec::new();
5853
5854        // VMOV S0, Rm
5855        let vmov = encode_vmov_core_sreg(true, &VfpReg::S0, rm)?;
5856        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5857
5858        // VCVT.F64.S32 Dd, S0 or VCVT.F64.U32 Dd, S0
5859        let dd_num = vfp_dreg_to_num(dd)?;
5860        let (vd, d) = encode_dreg(dd_num);
5861        let base = if signed { 0xEEB80B40 } else { 0xEEB80BC0 };
5862        let vcvt = base | (d << 22) | (vd << 12);
5863        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
5864
5865        Ok(bytes)
5866    }
5867
5868    /// Encode VCVT.F64.F32 Dd, Sm as Thumb-2
5869    fn encode_thumb_f64_promote_f32(&self, dd: &VfpReg, sm: &VfpReg) -> Result<Vec<u8>> {
5870        let dd_num = vfp_dreg_to_num(dd)?;
5871        let sm_num = vfp_sreg_to_num(sm)?;
5872        let (vd, d) = encode_dreg(dd_num);
5873        let (vm, m) = encode_sreg(sm_num);
5874
5875        let vcvt = 0xEEB70AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
5876        Ok(vfp_to_thumb_bytes(vcvt))
5877    }
5878
5879    /// Encode VCVT.S32/U32.F64 S0, Dm + VMOV Rd, S0 as Thumb-2
5880    fn encode_thumb_i32_trunc_f64(&self, rd: &Reg, dm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
5881        let mut bytes = Vec::new();
5882        let dm_num = vfp_dreg_to_num(dm)?;
5883        let (vm, m) = encode_dreg(dm_num);
5884
5885        // VCVT.S32.F64 S0, Dm or VCVT.U32.F64 S0, Dm
5886        let base = if signed { 0xEEBD0BC0 } else { 0xEEBC0BC0 };
5887        let vcvt = base | (m << 5) | vm;
5888        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
5889
5890        // VMOV Rd, S0
5891        let vmov = encode_vmov_core_sreg(false, &VfpReg::S0, rd)?;
5892        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
5893
5894        Ok(bytes)
5895    }
5896
5897    /// Encode F64 rounding pseudo-op as Thumb-2 via VCVT to integer and back
5898    /// Encode F64 rounding as Thumb-2.
5899    /// `mode`: FPSCR RMode — 0b00=nearest, 0b01=+inf(ceil), 0b10=-inf(floor), 0b11=zero(trunc)
5900    fn encode_thumb_f64_rounding(&self, dd: &VfpReg, dm: &VfpReg, mode: u8) -> Result<Vec<u8>> {
5901        let mut bytes = Vec::new();
5902        let dm_num = vfp_dreg_to_num(dm)?;
5903        let dd_num = vfp_dreg_to_num(dd)?;
5904        let (vm, m) = encode_dreg(dm_num);
5905        let (vd, d) = encode_dreg(dd_num);
5906
5907        if mode == 0b11 {
5908            // Trunc: VCVTR.S32.F64 — bit[7]=1, always truncates
5909            let vcvt_to_int = 0xEEBD0BC0 | (m << 5) | vm;
5910            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5911        } else {
5912            let rt: u32 = 12;
5913
5914            // VMRS R12, FPSCR
5915            let vmrs = 0xEEF10A10 | (rt << 12);
5916            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5917
5918            // BIC.W R12, R12, #(3 << 22)
5919            let bic_hw1: u16 = 0xF020 | ((rt as u16) & 0xF);
5920            let bic_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | 0x03;
5921            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5922            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5923
5924            // ORR.W R12, R12, #(mode << 22)
5925            if mode != 0 {
5926                let orr_hw1: u16 = 0xF040 | ((rt as u16) & 0xF);
5927                let orr_hw2: u16 = (0x05 << 12) | ((rt as u16) << 8) | (mode as u16);
5928                bytes.extend_from_slice(&orr_hw1.to_le_bytes());
5929                bytes.extend_from_slice(&orr_hw2.to_le_bytes());
5930            }
5931
5932            // VMSR FPSCR, R12
5933            let vmsr = 0xEEE10A10 | (rt << 12);
5934            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5935
5936            // VCVT.S32.F64 S0, Dm — non-R variant (bit[7]=0)
5937            let vcvt_to_int = 0xEEBD0B40 | (m << 5) | vm;
5938            bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_int));
5939
5940            // Restore FPSCR
5941            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmrs));
5942            bytes.extend_from_slice(&bic_hw1.to_le_bytes());
5943            bytes.extend_from_slice(&bic_hw2.to_le_bytes());
5944            bytes.extend_from_slice(&vfp_to_thumb_bytes(vmsr));
5945        }
5946
5947        // VCVT.F64.S32 Dd, S0
5948        let vcvt_to_float = 0xEEB80B40 | (d << 22) | (vd << 12);
5949        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt_to_float));
5950
5951        Ok(bytes)
5952    }
5953
5954    /// Encode F64 min/max as Thumb-2
5955    fn encode_thumb_f64_minmax(
5956        &self,
5957        dd: &VfpReg,
5958        dn: &VfpReg,
5959        dm: &VfpReg,
5960        is_min: bool,
5961    ) -> Result<Vec<u8>> {
5962        let mut bytes = Vec::new();
5963        let dn_num = vfp_dreg_to_num(dn)?;
5964        let dm_num = vfp_dreg_to_num(dm)?;
5965        let dd_num = vfp_dreg_to_num(dd)?;
5966
5967        // VMOV.F64 Dd, Dn
5968        let (vd, d) = encode_dreg(dd_num);
5969        let (vn, n) = encode_dreg(dn_num);
5970        let vmov_dn = 0xEEB00B40 | (d << 22) | (vd << 12) | (n << 5) | vn;
5971        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_dn));
5972
5973        // VCMP.F64 Dn, Dm
5974        let (vm, m) = encode_dreg(dm_num);
5975        let vcmp = 0xEEB40B40 | (n << 22) | (vn << 12) | (m << 5) | vm;
5976        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcmp));
5977
5978        // VMRS APSR_nzcv, FPSCR
5979        bytes.extend_from_slice(&vfp_to_thumb_bytes(0xEEF1FA10));
5980
5981        // IT GT (for min) or IT MI (for max)
5982        let cond: u16 = if is_min { 0xC } else { 0x4 };
5983        let it: u16 = 0xBF00 | (cond << 4) | 0x8;
5984        bytes.extend_from_slice(&it.to_le_bytes());
5985
5986        // VMOV{cond}.F64 Dd, Dm
5987        let vmov_dm = 0xEEB00B40 | (d << 22) | (vd << 12) | (m << 5) | vm;
5988        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov_dm));
5989
5990        Ok(bytes)
5991    }
5992
5993    /// Encode F64 copysign as Thumb-2
5994    fn encode_thumb_f64_copysign(&self, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<Vec<u8>> {
5995        let mut bytes = Vec::new();
5996
5997        // VMOV R0, R12, Dm (get sign source)
5998        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
5999            false,
6000            dm,
6001            &Reg::R0,
6002            &Reg::R12,
6003        )?));
6004
6005        // VMOV R1, R2, Dn (get magnitude source)
6006        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
6007            false,
6008            dn,
6009            &Reg::R1,
6010            &Reg::R2,
6011        )?));
6012
6013        // AND.W R12, R12, #0x80000000 (i=0, Rn=R12)
6014        let hw1: u16 = 0xF000 | 12;
6015        let hw2: u16 = (0x1 << 12) | (12 << 8) | 0x02;
6016        bytes.extend_from_slice(&hw1.to_le_bytes());
6017        bytes.extend_from_slice(&hw2.to_le_bytes());
6018
6019        // BIC.W R2, R2, #0x80000000 (i=0, Rn=R2)
6020        let hw1: u16 = 0xF020 | 2;
6021        let hw2: u16 = (0x1 << 12) | (2 << 8) | 0x02;
6022        bytes.extend_from_slice(&hw1.to_le_bytes());
6023        bytes.extend_from_slice(&hw2.to_le_bytes());
6024
6025        // ORR.W R2, R2, R12
6026        let hw1: u16 = 0xEA40 | 2;
6027        let hw2: u16 = (2 << 8) | 12;
6028        bytes.extend_from_slice(&hw1.to_le_bytes());
6029        bytes.extend_from_slice(&hw2.to_le_bytes());
6030
6031        // VMOV Dd, R1, R2
6032        bytes.extend_from_slice(&vfp_to_thumb_bytes(encode_vmov_core_dreg(
6033            true,
6034            dd,
6035            &Reg::R1,
6036            &Reg::R2,
6037        )?));
6038
6039        Ok(bytes)
6040    }
6041
6042    /// Encode VCVT.S32/U32.F32 + VMOV as Thumb-2
6043    fn encode_thumb_i32_trunc_f32(&self, rd: &Reg, sm: &VfpReg, signed: bool) -> Result<Vec<u8>> {
6044        let mut bytes = Vec::new();
6045
6046        let sm_num = vfp_sreg_to_num(sm)?;
6047        let (vd, d) = encode_sreg(sm_num);
6048        let (vm, m) = encode_sreg(sm_num);
6049        let base = if signed { 0xEEBD0AC0 } else { 0xEEBC0AC0 };
6050        let vcvt = base | (d << 22) | (vd << 12) | (m << 5) | vm;
6051        bytes.extend_from_slice(&vfp_to_thumb_bytes(vcvt));
6052
6053        // VMOV Rd, Sm
6054        let vmov = encode_vmov_core_sreg(false, sm, rd)?;
6055        bytes.extend_from_slice(&vfp_to_thumb_bytes(vmov));
6056
6057        Ok(bytes)
6058    }
6059
6060    // === Thumb-2 32-bit encoding helpers ===
6061
6062    /// Encode Thumb-2 32-bit ADD with immediate
6063    fn encode_thumb32_add(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6064        let rd_bits = reg_to_bits(rd);
6065        let rn_bits = reg_to_bits(rn);
6066
6067        // The `i:imm3:imm8` field is split the same way for both forms.
6068        let i_bit = (imm >> 11) & 1;
6069        let imm3 = (imm >> 8) & 0x7;
6070        let imm8 = imm & 0xFF;
6071
6072        let hw1_base = if imm <= 0xFF {
6073            // ADD.W (T3): the field is a ThumbExpandImm modified immediate. For
6074            // imm <= 0xFF (i:imm3 = 0000) it is the zero-extended byte, which is
6075            // correct — keep this form so existing encodings stay bit-identical.
6076            0xF100
6077        } else if imm <= 0xFFF {
6078            // ADDW (T4): a PLAIN 12-bit immediate (0..4095) — no ThumbExpandImm.
6079            // This is what makes `add sp, sp, #frame` correct for frame sizes
6080            // >= 256, which ADD.W (T3) would silently mis-encode (e.g. #256 -> #0).
6081            0xF200
6082        } else {
6083            return Err(synth_core::Error::synthesis(
6084                "ADD immediate > 0xFFF (4095) requires a multi-instruction sequence (not supported)",
6085            ));
6086        };
6087
6088        let hw1: u16 = (hw1_base | (i_bit << 10) | rn_bits) as u16;
6089        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6090
6091        let mut bytes = hw1.to_le_bytes().to_vec();
6092        bytes.extend_from_slice(&hw2.to_le_bytes());
6093        Ok(bytes)
6094    }
6095
6096    /// Encode Thumb-2 32-bit SUB with immediate
6097    fn encode_thumb32_sub(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6098        let rd_bits = reg_to_bits(rd);
6099        let rn_bits = reg_to_bits(rn);
6100
6101        let i_bit = (imm >> 11) & 1;
6102        let imm3 = (imm >> 8) & 0x7;
6103        let imm8 = imm & 0xFF;
6104
6105        let hw1_base = if imm <= 0xFF {
6106            // SUB.W (T3) modified immediate — correct for the zero-extended byte
6107            // (imm <= 0xFF). Kept bit-identical for existing encodings.
6108            0xF1A0
6109        } else if imm <= 0xFFF {
6110            // SUBW (T4): plain 12-bit immediate (0..4095). Makes
6111            // `sub sp, sp, #frame` correct for frame sizes >= 256.
6112            0xF2A0
6113        } else {
6114            return Err(synth_core::Error::synthesis(
6115                "SUB immediate > 0xFFF (4095) requires a multi-instruction sequence (not supported)",
6116            ));
6117        };
6118
6119        let hw1: u16 = (hw1_base | (i_bit << 10) | rn_bits) as u16;
6120        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6121
6122        let mut bytes = hw1.to_le_bytes().to_vec();
6123        bytes.extend_from_slice(&hw2.to_le_bytes());
6124        Ok(bytes)
6125    }
6126
6127    /// Encode Thumb-2 32-bit ADDS with immediate (sets flags)
6128    fn encode_thumb32_adds(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6129        let rd_bits = reg_to_bits(rd);
6130        let rn_bits = reg_to_bits(rn);
6131
6132        // ADDS.W (flag-setting) has only the modified-immediate form — error on
6133        // an un-encodable value rather than silently add the wrong constant.
6134        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6135            synth_core::Error::synthesis(
6136                "ADDS immediate is not a valid ThumbExpandImm — materialize into a register",
6137            )
6138        })?;
6139        let i_bit = (field >> 11) & 1;
6140        let imm3 = (field >> 8) & 0x7;
6141        let imm8 = field & 0xFF;
6142
6143        // ADDS.W Rd, Rn, #imm (with S=1)
6144        // First halfword: 1111 0 i 0 1000 1 Rn = F110 | i<<10 | Rn
6145        let hw1: u16 = (0xF110 | (i_bit << 10) | rn_bits) as u16;
6146        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6147
6148        let mut bytes = hw1.to_le_bytes().to_vec();
6149        bytes.extend_from_slice(&hw2.to_le_bytes());
6150        Ok(bytes)
6151    }
6152
6153    /// Encode Thumb-2 32-bit SUBS with immediate (sets flags)
6154    fn encode_thumb32_subs(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6155        let rd_bits = reg_to_bits(rd);
6156        let rn_bits = reg_to_bits(rn);
6157
6158        // SUBS.W (flag-setting) has only the modified-immediate form — error on
6159        // an un-encodable value rather than silently subtract the wrong constant.
6160        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6161            synth_core::Error::synthesis(
6162                "SUBS immediate is not a valid ThumbExpandImm — materialize into a register",
6163            )
6164        })?;
6165        let i_bit = (field >> 11) & 1;
6166        let imm3 = (field >> 8) & 0x7;
6167        let imm8 = field & 0xFF;
6168
6169        // SUBS.W Rd, Rn, #imm (with S=1)
6170        // First halfword: 1111 0 i 0 1101 1 Rn = F1B0 | i<<10 | Rn
6171        let hw1: u16 = (0xF1B0 | (i_bit << 10) | rn_bits) as u16;
6172        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6173
6174        let mut bytes = hw1.to_le_bytes().to_vec();
6175        bytes.extend_from_slice(&hw2.to_le_bytes());
6176        Ok(bytes)
6177    }
6178
6179    /// Encode Thumb-2 32-bit MOVW (16-bit immediate)
6180    ///
6181    /// # Contract (Verus-style)
6182    /// ```text
6183    /// requires rd <= R14
6184    /// ensures result.len() == 4
6185    /// ensures (imm & 0xFFFF) can be reconstructed from the encoding
6186    /// ```
6187    fn encode_thumb32_movw(&self, rd: &Reg, imm: u32) -> Result<Vec<u8>> {
6188        let rd_bits = reg_to_bits(rd);
6189        reg_bits_checked(rd_bits)?;
6190        let imm16 = imm & 0xFFFF;
6191
6192        // MOVW Rd, #imm16
6193        // 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
6194        let imm4 = (imm16 >> 12) & 0xF;
6195        let i_bit = (imm16 >> 11) & 1;
6196        let imm3 = (imm16 >> 8) & 0x7;
6197        let imm8 = imm16 & 0xFF;
6198
6199        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
6200        let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6201
6202        let mut bytes = hw1.to_le_bytes().to_vec();
6203        bytes.extend_from_slice(&hw2.to_le_bytes());
6204        encoding_contracts::verify_thumb32(&bytes);
6205        Ok(bytes)
6206    }
6207
6208    /// Encode Thumb-2 32-bit shift with immediate
6209    ///
6210    /// # Contract (Verus-style)
6211    /// ```text
6212    /// requires rd <= R14, rm <= R14
6213    /// ensures result.len() == 4
6214    /// ```
6215    fn encode_thumb32_shift(
6216        &self,
6217        rd: &Reg,
6218        rm: &Reg,
6219        shift: u32,
6220        shift_type: u8,
6221    ) -> Result<Vec<u8>> {
6222        let rd_bits = reg_to_bits(rd);
6223        let rm_bits = reg_to_bits(rm);
6224        reg_bits_checked(rd_bits)?;
6225        reg_bits_checked(rm_bits)?;
6226        let imm5 = shift & 0x1F;
6227        let imm2 = imm5 & 0x3;
6228        let imm3 = (imm5 >> 2) & 0x7;
6229
6230        // MOV.W Rd, Rm, <shift> #imm
6231        // EA4F 0 imm3 Rd imm2 type Rm
6232        let hw1: u16 = 0xEA4F;
6233        let hw2: u16 =
6234            ((imm3 << 12) | (rd_bits << 8) | (imm2 << 6) | ((shift_type as u32) << 4) | rm_bits)
6235                as u16;
6236
6237        let mut bytes = hw1.to_le_bytes().to_vec();
6238        bytes.extend_from_slice(&hw2.to_le_bytes());
6239        Ok(bytes)
6240    }
6241
6242    /// Encode Thumb-2 32-bit shift by register
6243    /// Encoding: 11111010 0xx0 Rn | 1111 Rd 0000 Rm
6244    /// shift_type: 00=LSL, 01=LSR, 10=ASR, 11=ROR
6245    fn encode_thumb32_shift_reg(
6246        &self,
6247        rd: &Reg,
6248        rn: &Reg,
6249        rm: &Reg,
6250        shift_type: u8,
6251    ) -> Result<Vec<u8>> {
6252        let rd_bits = reg_to_bits(rd);
6253        let rn_bits = reg_to_bits(rn);
6254        let rm_bits = reg_to_bits(rm);
6255
6256        // hw1: 1111 1010 0xx0 Rn
6257        let hw1: u16 = (0xFA00 | ((shift_type as u32) << 5) | rn_bits) as u16;
6258        // hw2: 1111 Rd 0000 Rm
6259        let hw2: u16 = (0xF000 | (rd_bits << 8) | rm_bits) as u16;
6260
6261        let mut bytes = hw1.to_le_bytes().to_vec();
6262        bytes.extend_from_slice(&hw2.to_le_bytes());
6263        Ok(bytes)
6264    }
6265
6266    /// Encode Thumb-2 32-bit CMP with immediate
6267    fn encode_thumb32_cmp_imm(&self, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6268        let rn_bits = reg_to_bits(rn);
6269
6270        // CMP.W has only the modified-immediate form (no plain-imm12 like ADDW),
6271        // so an un-encodable immediate MUST be materialized into a register by
6272        // the selector. Error rather than silently compare the wrong constant.
6273        let field = try_thumb_expand_imm(imm).ok_or_else(|| {
6274            synth_core::Error::synthesis(
6275                "CMP immediate is not a valid ThumbExpandImm — materialize into a register",
6276            )
6277        })?;
6278        let i_bit = (field >> 11) & 1;
6279        let imm3 = (field >> 8) & 0x7;
6280        let imm8 = field & 0xFF;
6281
6282        // CMP.W Rn, #imm
6283        let hw1: u16 = (0xF1B0 | (i_bit << 10) | rn_bits) as u16;
6284        let hw2: u16 = ((imm3 << 12) | 0x0F00 | imm8) as u16;
6285
6286        let mut bytes = hw1.to_le_bytes().to_vec();
6287        bytes.extend_from_slice(&hw2.to_le_bytes());
6288        Ok(bytes)
6289    }
6290
6291    /// #372/#382: resolve the base register AND residual immediate offset for an
6292    /// `I64Ldr`/`I64Str` whose address may carry an index register. Returns
6293    /// `(base, low_offset)`; the caller accesses the halves at `[base,
6294    /// #low_offset]` and `[base, #low_offset + 4]`.
6295    ///
6296    /// - Frame access (no `offset_reg`, e.g. a spilled local at `[SP, #off]`):
6297    ///   returns `(addr.base, off)` and emits NOTHING — byte-identical.
6298    /// - Memory access (`reg_imm(R11, addr, offset)` = `R11 + addr + offset`)
6299    ///   with `offset + 4 <= 0xFFF`: emits `ADD.W ip, base, index` and returns
6300    ///   `(ip, offset)`, folding `offset`/`offset+4` into the halves' imm12.
6301    ///   Byte-identical to the pre-#382 (#372) behavior.
6302    /// - Memory access with `offset + 4 > 0xFFF`: the imm12 form cannot hold the
6303    ///   high half's offset, so `encode_thumb32_ldr`'s `check_ldst_imm12` (#259)
6304    ///   rightly refused it and the WHOLE function was skipped (#382). Instead
6305    ///   MATERIALIZE the offset into the base: `ADD ip, index, #offset` (against
6306    ///   the read-only INDEX register, so `encode_thumb32_add_imm` never trips its
6307    ///   `rd==rn==R12` alias trap), then `ADD.W ip, ip, base` (+ R11), and return
6308    ///   `(ip, 0)` so the halves use `[ip, #0]` / `[ip, #4]`.
6309    ///
6310    /// The effective address is fully materialized into `ip` BEFORE the halves
6311    /// are accessed, so an `rdlo` aliasing the index register is safe.
6312    fn i64_effective_base(&self, bytes: &mut Vec<u8>, addr: &MemAddr) -> Result<(Reg, u32)> {
6313        let offset = if addr.offset < 0 {
6314            0u32
6315        } else {
6316            addr.offset as u32
6317        };
6318        match addr.offset_reg {
6319            Some(idx) => {
6320                let ip = Reg::R12;
6321                if offset.wrapping_add(4) > 0xFFF {
6322                    // Large static offset (#382): fold it (and R11) into ip so the
6323                    // imm12 halves stay in range instead of skipping the function.
6324                    // ADD ip, index, #offset  (index != ip → no add_imm alias trap)
6325                    bytes.extend_from_slice(&self.encode_thumb32_add_imm(&ip, &idx, offset)?);
6326                    // ADD.W ip, ip, base  (+ R11)
6327                    bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(
6328                        reg_to_bits(&ip),
6329                        reg_to_bits(&ip),
6330                        reg_to_bits(&addr.base),
6331                    )?);
6332                    Ok((ip, 0))
6333                } else {
6334                    // ADD.W ip, addr.base, idx  (Thumb-2, byte-verified vs as)
6335                    let hw1: u16 = 0xEB00 | reg_to_bits(&addr.base) as u16;
6336                    let hw2: u16 = 0x0C00 | reg_to_bits(&idx) as u16;
6337                    bytes.extend_from_slice(&hw1.to_le_bytes());
6338                    bytes.extend_from_slice(&hw2.to_le_bytes());
6339                    Ok((ip, offset))
6340                }
6341            }
6342            None => Ok((addr.base, offset)),
6343        }
6344    }
6345
6346    /// Encode Thumb-2 32-bit LDR
6347    fn encode_thumb32_ldr(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6348        let rd_bits = reg_to_bits(rd);
6349        let base_bits = reg_to_bits(base);
6350
6351        // LDR.W Rd, [Rn, #imm12]
6352        check_ldst_imm12(offset)?;
6353        let hw1: u16 = (0xF8D0 | base_bits) as u16;
6354        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6355
6356        let mut bytes = hw1.to_le_bytes().to_vec();
6357        bytes.extend_from_slice(&hw2.to_le_bytes());
6358        Ok(bytes)
6359    }
6360
6361    /// Encode Thumb-2 32-bit STR
6362    fn encode_thumb32_str(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6363        let rd_bits = reg_to_bits(rd);
6364        let base_bits = reg_to_bits(base);
6365
6366        // STR.W Rd, [Rn, #imm12]
6367        check_ldst_imm12(offset)?;
6368        let hw1: u16 = (0xF8C0 | base_bits) as u16;
6369        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6370
6371        let mut bytes = hw1.to_le_bytes().to_vec();
6372        bytes.extend_from_slice(&hw2.to_le_bytes());
6373        Ok(bytes)
6374    }
6375
6376    /// Encode Thumb-2 32-bit LDR with register offset: LDR.W Rd, [Rn, Rm]
6377    fn encode_thumb32_ldr_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6378        let rd_bits = reg_to_bits(rd);
6379        let base_bits = reg_to_bits(base);
6380        let rm_bits = reg_to_bits(offset_reg);
6381
6382        // LDR.W Rd, [Rn, Rm, LSL #0]
6383        // Encoding: 1111 1000 0101 Rn | Rt 0000 00 imm2 Rm
6384        // imm2 = 00 for no shift (LSL #0)
6385        let hw1: u16 = (0xF850 | base_bits) as u16;
6386        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6387
6388        let mut bytes = hw1.to_le_bytes().to_vec();
6389        bytes.extend_from_slice(&hw2.to_le_bytes());
6390        Ok(bytes)
6391    }
6392
6393    /// Encode Thumb-2 32-bit STR with register offset: STR.W Rd, [Rn, Rm]
6394    fn encode_thumb32_str_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6395        let rd_bits = reg_to_bits(rd);
6396        let base_bits = reg_to_bits(base);
6397        let rm_bits = reg_to_bits(offset_reg);
6398
6399        // STR.W Rd, [Rn, Rm, LSL #0]
6400        // Encoding: 1111 1000 0100 Rn | Rt 0000 00 imm2 Rm
6401        // imm2 = 00 for no shift (LSL #0)
6402        let hw1: u16 = (0xF840 | base_bits) as u16;
6403        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6404
6405        let mut bytes = hw1.to_le_bytes().to_vec();
6406        bytes.extend_from_slice(&hw2.to_le_bytes());
6407        Ok(bytes)
6408    }
6409
6410    // === Sub-word load/store Thumb-2 encoding helpers ===
6411
6412    /// Encode Thumb-2 32-bit LDRB with immediate: LDRB.W Rd, [Rn, #imm12]
6413    fn encode_thumb32_ldrb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6414        let rd_bits = reg_to_bits(rd);
6415        let base_bits = reg_to_bits(base);
6416        // LDRB.W Rd, [Rn, #imm12]: 1111 1000 1001 Rn | Rt imm12
6417        check_ldst_imm12(offset)?;
6418        let hw1: u16 = (0xF890 | base_bits) as u16;
6419        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6420        let mut bytes = hw1.to_le_bytes().to_vec();
6421        bytes.extend_from_slice(&hw2.to_le_bytes());
6422        Ok(bytes)
6423    }
6424
6425    /// Encode Thumb-2 32-bit LDRB with register: LDRB.W Rd, [Rn, Rm]
6426    fn encode_thumb32_ldrb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6427        let rd_bits = reg_to_bits(rd);
6428        let base_bits = reg_to_bits(base);
6429        let rm_bits = reg_to_bits(offset_reg);
6430        // LDRB.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0001 Rn | Rt 0000 00 imm2 Rm
6431        let hw1: u16 = (0xF810 | base_bits) as u16;
6432        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6433        let mut bytes = hw1.to_le_bytes().to_vec();
6434        bytes.extend_from_slice(&hw2.to_le_bytes());
6435        Ok(bytes)
6436    }
6437
6438    /// Encode Thumb-2 32-bit LDRSB with immediate: LDRSB.W Rd, [Rn, #imm12]
6439    fn encode_thumb32_ldrsb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6440        let rd_bits = reg_to_bits(rd);
6441        let base_bits = reg_to_bits(base);
6442        // LDRSB.W Rd, [Rn, #imm12]: 1111 1001 1001 Rn | Rt imm12
6443        check_ldst_imm12(offset)?;
6444        let hw1: u16 = (0xF990 | base_bits) as u16;
6445        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6446        let mut bytes = hw1.to_le_bytes().to_vec();
6447        bytes.extend_from_slice(&hw2.to_le_bytes());
6448        Ok(bytes)
6449    }
6450
6451    /// Encode Thumb-2 32-bit LDRSB with register: LDRSB.W Rd, [Rn, Rm]
6452    fn encode_thumb32_ldrsb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6453        let rd_bits = reg_to_bits(rd);
6454        let base_bits = reg_to_bits(base);
6455        let rm_bits = reg_to_bits(offset_reg);
6456        // LDRSB.W Rd, [Rn, Rm, LSL #0]: 1111 1001 0001 Rn | Rt 0000 00 imm2 Rm
6457        let hw1: u16 = (0xF910 | base_bits) as u16;
6458        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6459        let mut bytes = hw1.to_le_bytes().to_vec();
6460        bytes.extend_from_slice(&hw2.to_le_bytes());
6461        Ok(bytes)
6462    }
6463
6464    /// Encode Thumb-2 32-bit LDRH with immediate: LDRH.W Rd, [Rn, #imm12]
6465    fn encode_thumb32_ldrh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6466        let rd_bits = reg_to_bits(rd);
6467        let base_bits = reg_to_bits(base);
6468        // LDRH.W Rd, [Rn, #imm12]: 1111 1000 1011 Rn | Rt imm12
6469        check_ldst_imm12(offset)?;
6470        let hw1: u16 = (0xF8B0 | base_bits) as u16;
6471        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6472        let mut bytes = hw1.to_le_bytes().to_vec();
6473        bytes.extend_from_slice(&hw2.to_le_bytes());
6474        Ok(bytes)
6475    }
6476
6477    /// Encode Thumb-2 32-bit LDRH with register: LDRH.W Rd, [Rn, Rm]
6478    fn encode_thumb32_ldrh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6479        let rd_bits = reg_to_bits(rd);
6480        let base_bits = reg_to_bits(base);
6481        let rm_bits = reg_to_bits(offset_reg);
6482        // LDRH.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0011 Rn | Rt 0000 00 imm2 Rm
6483        let hw1: u16 = (0xF830 | base_bits) as u16;
6484        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6485        let mut bytes = hw1.to_le_bytes().to_vec();
6486        bytes.extend_from_slice(&hw2.to_le_bytes());
6487        Ok(bytes)
6488    }
6489
6490    /// Encode Thumb-2 32-bit LDRSH with immediate: LDRSH.W Rd, [Rn, #imm12]
6491    fn encode_thumb32_ldrsh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6492        let rd_bits = reg_to_bits(rd);
6493        let base_bits = reg_to_bits(base);
6494        // LDRSH.W Rd, [Rn, #imm12]: 1111 1001 1011 Rn | Rt imm12
6495        check_ldst_imm12(offset)?;
6496        let hw1: u16 = (0xF9B0 | base_bits) as u16;
6497        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6498        let mut bytes = hw1.to_le_bytes().to_vec();
6499        bytes.extend_from_slice(&hw2.to_le_bytes());
6500        Ok(bytes)
6501    }
6502
6503    /// Encode Thumb-2 32-bit LDRSH with register: LDRSH.W Rd, [Rn, Rm]
6504    fn encode_thumb32_ldrsh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6505        let rd_bits = reg_to_bits(rd);
6506        let base_bits = reg_to_bits(base);
6507        let rm_bits = reg_to_bits(offset_reg);
6508        // LDRSH.W Rd, [Rn, Rm, LSL #0]: 1111 1001 0011 Rn | Rt 0000 00 imm2 Rm
6509        let hw1: u16 = (0xF930 | base_bits) as u16;
6510        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6511        let mut bytes = hw1.to_le_bytes().to_vec();
6512        bytes.extend_from_slice(&hw2.to_le_bytes());
6513        Ok(bytes)
6514    }
6515
6516    /// Encode Thumb-2 32-bit STRB with immediate: STRB.W Rd, [Rn, #imm12]
6517    fn encode_thumb32_strb_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6518        let rd_bits = reg_to_bits(rd);
6519        let base_bits = reg_to_bits(base);
6520        // STRB.W Rd, [Rn, #imm12]: 1111 1000 1000 Rn | Rt imm12
6521        check_ldst_imm12(offset)?;
6522        let hw1: u16 = (0xF880 | base_bits) as u16;
6523        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6524        let mut bytes = hw1.to_le_bytes().to_vec();
6525        bytes.extend_from_slice(&hw2.to_le_bytes());
6526        Ok(bytes)
6527    }
6528
6529    /// Encode Thumb-2 32-bit STRB with register: STRB.W Rd, [Rn, Rm]
6530    fn encode_thumb32_strb_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6531        let rd_bits = reg_to_bits(rd);
6532        let base_bits = reg_to_bits(base);
6533        let rm_bits = reg_to_bits(offset_reg);
6534        // STRB.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0000 Rn | Rt 0000 00 imm2 Rm
6535        let hw1: u16 = (0xF800 | base_bits) as u16;
6536        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6537        let mut bytes = hw1.to_le_bytes().to_vec();
6538        bytes.extend_from_slice(&hw2.to_le_bytes());
6539        Ok(bytes)
6540    }
6541
6542    /// Encode Thumb-2 32-bit STRH with immediate: STRH.W Rd, [Rn, #imm12]
6543    fn encode_thumb32_strh_imm(&self, rd: &Reg, base: &Reg, offset: u32) -> Result<Vec<u8>> {
6544        let rd_bits = reg_to_bits(rd);
6545        let base_bits = reg_to_bits(base);
6546        // STRH.W Rd, [Rn, #imm12]: 1111 1000 1010 Rn | Rt imm12
6547        check_ldst_imm12(offset)?;
6548        let hw1: u16 = (0xF8A0 | base_bits) as u16;
6549        let hw2: u16 = ((rd_bits << 12) | (offset & 0xFFF)) as u16;
6550        let mut bytes = hw1.to_le_bytes().to_vec();
6551        bytes.extend_from_slice(&hw2.to_le_bytes());
6552        Ok(bytes)
6553    }
6554
6555    /// Encode Thumb-2 32-bit STRH with register: STRH.W Rd, [Rn, Rm]
6556    fn encode_thumb32_strh_reg(&self, rd: &Reg, base: &Reg, offset_reg: &Reg) -> Result<Vec<u8>> {
6557        let rd_bits = reg_to_bits(rd);
6558        let base_bits = reg_to_bits(base);
6559        let rm_bits = reg_to_bits(offset_reg);
6560        // STRH.W Rd, [Rn, Rm, LSL #0]: 1111 1000 0010 Rn | Rt 0000 00 imm2 Rm
6561        let hw1: u16 = (0xF820 | base_bits) as u16;
6562        let hw2: u16 = ((rd_bits << 12) | rm_bits) as u16;
6563        let mut bytes = hw1.to_le_bytes().to_vec();
6564        bytes.extend_from_slice(&hw2.to_le_bytes());
6565        Ok(bytes)
6566    }
6567
6568    /// Encode Thumb-2 32-bit ADD with immediate: ADD.W Rd, Rn, #imm
6569    fn encode_thumb32_add_imm(&self, rd: &Reg, rn: &Reg, imm: u32) -> Result<Vec<u8>> {
6570        let rd_bits = reg_to_bits(rd);
6571        let rn_bits = reg_to_bits(rn);
6572
6573        // For small immediates, use ADD.W Rd, Rn, #imm12
6574        // Encoding: 1111 0 i 0 1 0 0 0 S Rn | 0 imm3 Rd imm8
6575        // S = 0 (don't update flags)
6576        // The 12-bit immediate is encoded as: i:imm3:imm8
6577        // For simplicity, we only support imm <= 0xFFF (direct encoding)
6578        if imm <= 0xFFF {
6579            let i_bit = (imm >> 11) & 1;
6580            let imm3 = (imm >> 8) & 0x7;
6581            let imm8 = imm & 0xFF;
6582
6583            let hw1: u16 = (0xF100 | (i_bit << 10) | rn_bits) as u16;
6584            let hw2: u16 = ((imm3 << 12) | (rd_bits << 8) | imm8) as u16;
6585
6586            let mut bytes = hw1.to_le_bytes().to_vec();
6587            bytes.extend_from_slice(&hw2.to_le_bytes());
6588            Ok(bytes)
6589        } else {
6590            // Out-of-range immediate (> 0xFFF): materialize it into a scratch
6591            // register, then ADD.W Rd, Rn, scratch. This is the #180/#185
6592            // "encoder must produce a legal sequence, not assert" class — see #350.
6593            //
6594            // Scratch choice (must NEVER equal Rn, or Rn would be clobbered before
6595            // the ADD reads it):
6596            //   - rd != rn  => use rd itself (rn is untouched, since rd != rn).
6597            //   - rd == rn  => use R12/IP (the reserved encoder scratch). rd/rn are
6598            //                  never R12 (R12 is non-allocatable), so it can't alias.
6599            //
6600            // The materialized value is the same whether or not MOVT is emitted, so
6601            // the byte length depends only on `imm` (and rd==rn) — the size probe and
6602            // the final emit therefore agree (mandatory: the function is encoded twice).
6603            let scratch: u32 = if rd_bits == rn_bits {
6604                12 // R12/IP — in-place add, can't use rd because rd == rn
6605            } else {
6606                rd_bits // rn is preserved because rd != rn
6607            };
6608            // Invariant: the scratch must never alias Rn (would clobber it before
6609            // the ADD reads it). Unreachable in real codegen (rd/rn are never R12,
6610            // which is reserved encoder scratch), but the encoder is also driven by
6611            // the `encoder_no_panic` fuzz harness with ARBITRARY registers — incl.
6612            // rd==rn==R12, which makes scratch (R12) alias Rn. The encoder contract
6613            // (#180/#185) is Ok-or-Err, never a panic, so return a typed error
6614            // instead of asserting. #350 follow-up.
6615            if scratch == rn_bits {
6616                return Err(synth_core::Error::synthesis(format!(
6617                    "ADD #imm: cannot lower #{imm:#x} for Rd==Rn==R12 — no free scratch \
6618                     register (R12 is the reserved encoder scratch and aliases Rn here)"
6619                )));
6620            }
6621
6622            let lo16 = imm & 0xFFFF;
6623            let hi16 = (imm >> 16) & 0xFFFF;
6624
6625            let mut bytes = self.encode_thumb32_movw_raw(scratch, lo16)?;
6626            if hi16 != 0 {
6627                bytes.extend_from_slice(&self.encode_thumb32_movt_raw(scratch, hi16)?);
6628            }
6629            bytes.extend_from_slice(&self.encode_thumb32_add_reg_raw(rd_bits, rn_bits, scratch)?);
6630            Ok(bytes)
6631        }
6632    }
6633
6634    // === Raw encoding helpers for POPCNT (take register numbers directly) ===
6635
6636    /// Encode Thumb-2 32-bit MOVW (16-bit immediate) - raw version
6637    ///
6638    /// # Contract (Verus-style)
6639    /// ```text
6640    /// requires rd <= 14, imm16 <= 0xFFFF
6641    /// ensures result.len() == 4
6642    /// ```
6643    fn encode_thumb32_movw_raw(&self, rd: u32, imm16: u32) -> Result<Vec<u8>> {
6644        reg_bits_checked(rd)?;
6645        encoding_contracts::verify_imm16(imm16);
6646        // MOVW Rd, #imm16
6647        // 1111 0 i 10 0 1 0 0 imm4 | 0 imm3 Rd imm8
6648        let imm16 = imm16 & 0xFFFF;
6649        let imm4 = (imm16 >> 12) & 0xF;
6650        let i_bit = (imm16 >> 11) & 1;
6651        let imm3 = (imm16 >> 8) & 0x7;
6652        let imm8 = imm16 & 0xFF;
6653
6654        let hw1: u16 = (0xF240 | (i_bit << 10) | imm4) as u16;
6655        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6656
6657        let mut bytes = hw1.to_le_bytes().to_vec();
6658        bytes.extend_from_slice(&hw2.to_le_bytes());
6659        encoding_contracts::verify_thumb32(&bytes);
6660        Ok(bytes)
6661    }
6662
6663    /// Encode Thumb-2 32-bit MOVT (move top 16 bits) - raw version
6664    ///
6665    /// # Contract (Verus-style)
6666    /// ```text
6667    /// requires rd <= 14, imm16 <= 0xFFFF
6668    /// ensures result.len() == 4
6669    /// ```
6670    fn encode_thumb32_movt_raw(&self, rd: u32, imm16: u32) -> Result<Vec<u8>> {
6671        reg_bits_checked(rd)?;
6672        encoding_contracts::verify_imm16(imm16);
6673        // MOVT Rd, #imm16
6674        // 1111 0 i 10 1 1 0 0 imm4 | 0 imm3 Rd imm8
6675        let imm16 = imm16 & 0xFFFF;
6676        let imm4 = (imm16 >> 12) & 0xF;
6677        let i_bit = (imm16 >> 11) & 1;
6678        let imm3 = (imm16 >> 8) & 0x7;
6679        let imm8 = imm16 & 0xFF;
6680
6681        let hw1: u16 = (0xF2C0 | (i_bit << 10) | imm4) as u16;
6682        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6683
6684        let mut bytes = hw1.to_le_bytes().to_vec();
6685        bytes.extend_from_slice(&hw2.to_le_bytes());
6686        encoding_contracts::verify_thumb32(&bytes);
6687        Ok(bytes)
6688    }
6689
6690    /// Encode Thumb-2 32-bit LSR (logical shift right) with immediate - raw version
6691    fn encode_thumb32_lsr_raw(&self, rd: u32, rm: u32, shift: u32) -> Result<Vec<u8>> {
6692        // MOV.W Rd, Rm, LSR #imm
6693        // EA4F 0 imm3 Rd imm2 01 Rm
6694        let imm5 = shift & 0x1F;
6695        let imm2 = imm5 & 0x3;
6696        let imm3 = (imm5 >> 2) & 0x7;
6697
6698        let hw1: u16 = 0xEA4F;
6699        let hw2: u16 = ((imm3 << 12) | (rd << 8) | (imm2 << 6) | (0b01 << 4) | rm) as u16;
6700
6701        let mut bytes = hw1.to_le_bytes().to_vec();
6702        bytes.extend_from_slice(&hw2.to_le_bytes());
6703        Ok(bytes)
6704    }
6705
6706    /// Encode Thumb-2 32-bit AND (register) - raw version
6707    fn encode_thumb32_and_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6708        // AND.W Rd, Rn, Rm
6709        // EA00 Rn | 0 Rd 00 00 Rm
6710        let hw1: u16 = (0xEA00 | rn) as u16;
6711        let hw2: u16 = ((rd << 8) | rm) as u16;
6712
6713        let mut bytes = hw1.to_le_bytes().to_vec();
6714        bytes.extend_from_slice(&hw2.to_le_bytes());
6715        Ok(bytes)
6716    }
6717
6718    /// Encode Thumb-2 32-bit AND with immediate - raw version
6719    fn encode_thumb32_and_imm_raw(&self, rd: u32, rn: u32, imm: u32) -> Result<Vec<u8>> {
6720        // AND.W Rd, Rn, #<modified_immediate>
6721        // For small immediates (0-255), the encoding is simpler
6722        // F0 00 Rn | 0 imm3 Rd imm8
6723        let i_bit = (imm >> 11) & 1;
6724        let imm3 = (imm >> 8) & 0x7;
6725        let imm8 = imm & 0xFF;
6726
6727        let hw1: u16 = (0xF000 | (i_bit << 10) | rn) as u16;
6728        let hw2: u16 = ((imm3 << 12) | (rd << 8) | imm8) as u16;
6729
6730        let mut bytes = hw1.to_le_bytes().to_vec();
6731        bytes.extend_from_slice(&hw2.to_le_bytes());
6732        Ok(bytes)
6733    }
6734
6735    /// Encode Thumb-2 32-bit SUB (register) - raw version
6736    fn encode_thumb32_sub_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6737        // SUB.W Rd, Rn, Rm
6738        // EBA0 Rn | 0 Rd 00 00 Rm
6739        let hw1: u16 = (0xEBA0 | rn) as u16;
6740        let hw2: u16 = ((rd << 8) | rm) as u16;
6741
6742        let mut bytes = hw1.to_le_bytes().to_vec();
6743        bytes.extend_from_slice(&hw2.to_le_bytes());
6744        Ok(bytes)
6745    }
6746
6747    /// Encode Thumb-2 32-bit ADD (register) - raw version
6748    fn encode_thumb32_add_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6749        // ADD.W Rd, Rn, Rm
6750        // EB00 Rn | 0 Rd 00 00 Rm
6751        let hw1: u16 = (0xEB00 | rn) as u16;
6752        let hw2: u16 = ((rd << 8) | rm) as u16;
6753
6754        let mut bytes = hw1.to_le_bytes().to_vec();
6755        bytes.extend_from_slice(&hw2.to_le_bytes());
6756        Ok(bytes)
6757    }
6758
6759    /// Encode Thumb-2 32-bit ADDS (register, flag-setting) - raw version.
6760    /// Used as the high-register fallback for `ArmOp::Adds` (i64 low-word add)
6761    /// so R8-R11 pair operands don't overflow the 16-bit field — #178/#180.
6762    fn encode_thumb32_adds_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6763        // ADDS.W Rd, Rn, Rm (T3, S=1): EB10 Rn | 0 Rd 00 00 Rm
6764        let hw1: u16 = (0xEB10 | rn) as u16;
6765        let hw2: u16 = ((rd << 8) | rm) as u16;
6766        let mut bytes = hw1.to_le_bytes().to_vec();
6767        bytes.extend_from_slice(&hw2.to_le_bytes());
6768        Ok(bytes)
6769    }
6770
6771    /// Encode Thumb-2 32-bit SUBS (register, flag-setting) - raw version.
6772    /// High-register fallback for `ArmOp::Subs` (i64 low-word subtract) — #178/#180.
6773    fn encode_thumb32_subs_reg_raw(&self, rd: u32, rn: u32, rm: u32) -> Result<Vec<u8>> {
6774        // SUBS.W Rd, Rn, Rm (T3, S=1): EBB0 Rn | 0 Rd 00 00 Rm
6775        let hw1: u16 = (0xEBB0 | rn) as u16;
6776        let hw2: u16 = ((rd << 8) | rm) as u16;
6777        let mut bytes = hw1.to_le_bytes().to_vec();
6778        bytes.extend_from_slice(&hw2.to_le_bytes());
6779        Ok(bytes)
6780    }
6781
6782    /// Encode a sequence of ARM instructions
6783    pub fn encode_sequence(&self, ops: &[ArmOp]) -> Result<Vec<u8>> {
6784        let mut code = Vec::new();
6785
6786        for op in ops {
6787            let encoded = self.encode(op)?;
6788            code.extend_from_slice(&encoded);
6789        }
6790
6791        Ok(code)
6792    }
6793}
6794
6795/// Convert register to bit encoding (0-15)
6796/// Reverse of the ARMv7-M `ThumbExpandImm`: given a 32-bit immediate, return the
6797/// 12-bit `i:imm3:imm8` field if it is a representable modified immediate, else
6798/// `None` (the caller must materialize the value into a register). This is the
6799/// shared correct path for the data-processing immediate encoders — without it
6800/// they pack raw bits and silently mis-encode any value `> 0xFF` that isn't a
6801/// modified immediate (the silent-miscompile class behind #251/#253/#255).
6802fn try_thumb_expand_imm(value: u32) -> Option<u32> {
6803    // i:imm3 = 0000 → 8-bit value, zero-extended (00000000 00000000 00000000 XY).
6804    if value <= 0xFF {
6805        return Some(value);
6806    }
6807    let b0 = value & 0xFF; // byte 0
6808    let b1 = (value >> 8) & 0xFF; // byte 1
6809    // 0x00XY00XY (i:imm3 = 0001) — XY in bytes 0 and 2
6810    if value == (b0 << 16) | b0 {
6811        return Some(0x100 | b0);
6812    }
6813    // 0xXY00XY00 (i:imm3 = 0010) — XY in bytes 1 and 3
6814    if value == (b1 << 24) | (b1 << 8) {
6815        return Some(0x200 | b1);
6816    }
6817    // 0xXYXYXYXY (i:imm3 = 0011) — XY in all four bytes
6818    if value == (b0 << 24) | (b0 << 16) | (b0 << 8) | b0 {
6819        return Some(0x300 | b0);
6820    }
6821    // An 8-bit value with bit 7 set, rotated right by 8..=31. `rotate_left(rot)`
6822    // undoes the encoded right rotation; if the result is `1bbbbbbb` (0x80..=0xFF)
6823    // the value is representable. imm12[11:7] = rot, imm12[6:0] = low 7 bits.
6824    for rot in 8..=31u32 {
6825        let unrot = value.rotate_left(rot);
6826        if (0x80..=0xFF).contains(&unrot) {
6827            return Some((rot << 7) | (unrot & 0x7F));
6828        }
6829    }
6830    None
6831}
6832
6833/// Guard a Thumb-2 `LDR/STR Rd, [Rn, #imm12]` offset. The imm12 form supports
6834/// `0..=4095`; a larger offset must be materialized into a register by the
6835/// selector (register-offset addressing). Returning `Err` rather than silently
6836/// masking `offset & 0xFFF` closes the wrong-address miscompile class (#259,
6837/// the load/store sibling of #253/#255).
6838fn check_ldst_imm12(offset: u32) -> Result<()> {
6839    if offset > 0xFFF {
6840        Err(synth_core::Error::synthesis(
6841            "load/store immediate offset > 0xFFF (4095) — materialize the offset into a register",
6842        ))
6843    } else {
6844        Ok(())
6845    }
6846}
6847
6848fn reg_to_bits(reg: &Reg) -> u32 {
6849    match reg {
6850        Reg::R0 => 0,
6851        Reg::R1 => 1,
6852        Reg::R2 => 2,
6853        Reg::R3 => 3,
6854        Reg::R4 => 4,
6855        Reg::R5 => 5,
6856        Reg::R6 => 6,
6857        Reg::R7 => 7,
6858        Reg::R8 => 8,
6859        Reg::R9 => 9,
6860        Reg::R10 => 10,
6861        Reg::R11 => 11,
6862        Reg::R12 => 12,
6863        Reg::SP => 13,
6864        Reg::LR => 14,
6865        Reg::PC => 15,
6866    }
6867}
6868
6869// ======================================================================
6870// #610 — i64 fixed-ABI expansion wrappers.
6871//
6872// The hand-written multi-instruction i64 cores (rotl/rotr and the div/rem
6873// shift-subtract loops) compute in FIXED low registers. Before #610 the
6874// div/rem arms ignored their operand fields outright (hardcoded R0:R1 /
6875// R2:R3 in, result to R0:R1) and the rot arms used R3/R4 scratch that
6876// collided with selector-assigned registers — then restored the saved
6877// scratch OVER the result (`POP {R4}` with rd_lo == R4), so the op
6878// returned the caller's stale register: 0 for every input under qemu.
6879//
6880// These wrappers make each core honor its register parameters:
6881//   1. save R0-R3,
6882//   2. marshal the operand registers into the core's fixed input regs via
6883//      the stack (permutation-safe: every source is read before any fixed
6884//      register is written),
6885//   3. run the fixed-reg core (self-preserving for R4+; R12 is encoder
6886//      scratch and never allocatable, #212),
6887//   4. MOV the result pair from R0:R1 into the selector's rd pair,
6888//   5. restore R0-R3, skipping any register the result now occupies.
6889//
6890// All emitted lengths are register-independent so the optimized path's
6891// byte-size estimator (`estimate_arm_byte_size`, pinned by the
6892// estimator↔encoder agreement oracle #498/#511) stays a constant per op.
6893// ======================================================================
6894
6895/// Steps 1+2: `PUSH {R0-R3}`, then marshal `srcs` (operand registers, any of
6896/// R0-R12) into `R0..R<n>` via individual stack pushes. Sources are all read
6897/// before any destination register is written, so arbitrary source/target
6898/// permutations (including operands living in R0-R3) are safe.
6899fn emit_i64_fixed_abi_entry(bytes: &mut Vec<u8>, srcs: &[&Reg]) {
6900    debug_assert!(srcs.len() <= 4);
6901    // PUSH {R0-R3} — save the caller-visible low registers.
6902    bytes.extend_from_slice(&0xB40Fu16.to_le_bytes());
6903    // STR src, [SP, #-4]! — push in reverse so srcs[0] ends up on top.
6904    for src in srcs.iter().rev() {
6905        let rt = reg_to_bits(src) as u16;
6906        bytes.extend_from_slice(&0xF84Du16.to_le_bytes());
6907        bytes.extend_from_slice(&((rt << 12) | 0x0D04).to_le_bytes());
6908    }
6909    // POP {Ri} — Ri := srcs[i].
6910    for i in 0..srcs.len() as u16 {
6911        bytes.extend_from_slice(&(0xBC00u16 | (1u16 << i)).to_le_bytes());
6912    }
6913}
6914
6915/// Steps 4+5: move the core's R0:R1 result into the selector's rd pair, then
6916/// restore the R0-R3 saved by [`emit_i64_fixed_abi_entry`], skipping any
6917/// register the result now lives in (its saved caller word is discarded).
6918fn emit_i64_fixed_abi_exit(bytes: &mut Vec<u8>, rdlo: &Reg, rdhi: &Reg) -> Result<()> {
6919    let lo = reg_to_bits(rdlo);
6920    let hi = reg_to_bits(rdhi);
6921    if lo == 1 && hi == 0 {
6922        // A fully swapped pair would clobber one half in either MOV order.
6923        // Selector pairs are consecutive (lo, lo+1), so this cannot occur.
6924        return Err(synth_core::Error::synthesis(
6925            "i64 expansion: swapped result pair (rd_lo=R1, rd_hi=R0) is unsupported (#610)",
6926        ));
6927    }
6928    let mov16 = |bytes: &mut Vec<u8>, rd: u32, rm: u32| {
6929        let d = ((rd >> 3) & 1) as u16;
6930        bytes.extend_from_slice(
6931            &(0x4600u16 | (d << 7) | ((rm as u16) << 3) | ((rd & 7) as u16)).to_le_bytes(),
6932        );
6933    };
6934    if hi == 0 {
6935        // rd_hi is R0: read R0 into rd_lo BEFORE overwriting R0 with R1.
6936        mov16(bytes, lo, 0);
6937        mov16(bytes, hi, 1);
6938    } else {
6939        // rd_lo may be R1: read R1 into rd_hi BEFORE overwriting R1 with R0.
6940        mov16(bytes, hi, 1);
6941        mov16(bytes, lo, 0);
6942    }
6943    for i in 0..4u32 {
6944        if i == lo || i == hi {
6945            // The result lives here — drop the saved caller word.
6946            bytes.extend_from_slice(&0xB001u16.to_le_bytes()); // ADD SP, #4
6947        } else {
6948            bytes.extend_from_slice(&(0xBC00u16 | (1u16 << i)).to_le_bytes()); // POP {Ri}
6949        }
6950    }
6951    Ok(())
6952}
6953
6954/// WASM `i64.div_*` / `i64.rem_*` by zero must trap, matching the i32 path's
6955/// cmp/bne/udf guard. Emitted after marshaling, when the divisor pair is in
6956/// R2:R3: `ORRS R12, R2, R3` — `BNE` over a `UDF #0` when nonzero.
6957fn emit_i64_divisor_zero_trap(bytes: &mut Vec<u8>) {
6958    bytes.extend_from_slice(&0xEA52u16.to_le_bytes()); // ORRS.W R12, R2, R3
6959    bytes.extend_from_slice(&0x0C03u16.to_le_bytes());
6960    bytes.extend_from_slice(&0xD100u16.to_le_bytes()); // BNE.N +0 (skip the UDF)
6961    bytes.extend_from_slice(&0xDE00u16.to_le_bytes()); // UDF #0 — divide by zero
6962}
6963
6964/// Fallible form of the `verify_reg_bits` contract. PC (R15) is not a valid
6965/// data operand for the Thumb-2 encodings that use this guard (SDIV/UDIV/MLS/…
6966/// are UNPREDICTABLE with PC). Synth's own codegen never emits PC there, but
6967/// the encoder must stay *total* over arbitrary `ArmOp` inputs — the fuzz
6968/// harness (`encoder_no_panic`) requires Ok-or-Err, never a panic. Pre-fix, the
6969/// `debug_assert` in `verify_reg_bits` aborted under `-Cdebug-assertions`.
6970/// Returns a typed Err instead. See #185.
6971fn reg_bits_checked(bits: u32) -> Result<()> {
6972    if bits > 14 {
6973        return Err(synth_core::Error::synthesis(format!(
6974            "register bits {bits} (PC/R15) is not a valid operand for this Thumb-2 encoding"
6975        )));
6976    }
6977    Ok(())
6978}
6979
6980/// Try to encode a 32-bit value as an ARM rotated immediate (imm8 ROR 2*rot4).
6981/// Returns Some((encoded_bits, 1)) if representable, None otherwise.
6982fn try_encode_rotated_imm(val: u32) -> Option<(u32, u32)> {
6983    if val == 0 {
6984        return Some((0, 1));
6985    }
6986    for rot in 0..16u32 {
6987        let shift = rot * 2;
6988        // Rotate left by shift (undo the ROR) to see if result fits in 8 bits
6989        let unrotated = val.rotate_left(shift);
6990        if unrotated <= 0xFF {
6991            // Encoded as: rot4(4 bits) | imm8(8 bits) = rotate_imm << 8 | imm8
6992            return Some(((rot << 8) | unrotated, 1));
6993        }
6994    }
6995    None
6996}
6997
6998/// Encode operand2 field and return (bits, immediate_flag).
6999/// For ARM32 mode, immediates use the rotated-immediate encoding (imm8 ROR 2*rot4).
7000/// Panics if an immediate value cannot be represented. Callers that need large
7001/// immediates should use MOVW/MOVT instead of Operand2::Imm.
7002fn encode_operand2(op2: &Operand2) -> Result<(u32, u32)> {
7003    match op2 {
7004        Operand2::Imm(val) => {
7005            let uval = *val as u32;
7006            // Attempt rotated-immediate encoding (ARM32 Operand2)
7007            if let Some(encoded) = try_encode_rotated_imm(uval) {
7008                Ok(encoded)
7009            } else {
7010                // #378-class honesty: an immediate that can't be expressed as an
7011                // ARM32 rotated immediate is an INTERNAL selector bug — large
7012                // constants must be materialized via MOVW/MOVT, not passed here.
7013                // FAIL HONESTLY with an Err rather than silently masking to
7014                // `uval & 0xFF` and emitting a WRONG immediate. The encoder is
7015                // Ok-or-Err, never corrupt (#180/#185); a loud Err is also why
7016                // this is an Err and not a panic (the `encoder_no_panic` fuzz
7017                // contract — malformed/oversized input must degrade, not crash).
7018                Err(synth_core::Error::synthesis(format!(
7019                    "encode_operand2: immediate {uval:#x} ({val}) is not an ARM32 \
7020                     rotated immediate — the selector must materialize large \
7021                     constants via MOVW/MOVT"
7022                )))
7023            }
7024        }
7025
7026        Operand2::Reg(reg) => {
7027            let reg_bits = reg_to_bits(reg);
7028            Ok((reg_bits, 0)) // I=0 for register
7029        }
7030
7031        Operand2::RegShift {
7032            rm,
7033            shift: _,
7034            amount,
7035        } => {
7036            // Simplified encoding with shift
7037            let rm_bits = reg_to_bits(rm);
7038            let shift_bits = (*amount & 0x1F) << 7;
7039            Ok((shift_bits | rm_bits, 0))
7040        }
7041    }
7042}
7043
7044/// Encode memory address to (base_reg, offset)
7045fn encode_mem_addr(addr: &MemAddr) -> (u32, u32) {
7046    let base_bits = reg_to_bits(&addr.base);
7047    let offset_bits = (addr.offset as u32) & 0xFFF; // 12-bit offset
7048    (base_bits, offset_bits)
7049}
7050
7051/// S-register number: S0=0, S1=1, ..., S31=31
7052fn vfp_sreg_to_num(reg: &VfpReg) -> Result<u32> {
7053    match reg {
7054        VfpReg::S0 => Ok(0),
7055        VfpReg::S1 => Ok(1),
7056        VfpReg::S2 => Ok(2),
7057        VfpReg::S3 => Ok(3),
7058        VfpReg::S4 => Ok(4),
7059        VfpReg::S5 => Ok(5),
7060        VfpReg::S6 => Ok(6),
7061        VfpReg::S7 => Ok(7),
7062        VfpReg::S8 => Ok(8),
7063        VfpReg::S9 => Ok(9),
7064        VfpReg::S10 => Ok(10),
7065        VfpReg::S11 => Ok(11),
7066        VfpReg::S12 => Ok(12),
7067        VfpReg::S13 => Ok(13),
7068        VfpReg::S14 => Ok(14),
7069        VfpReg::S15 => Ok(15),
7070        VfpReg::S16 => Ok(16),
7071        VfpReg::S17 => Ok(17),
7072        VfpReg::S18 => Ok(18),
7073        VfpReg::S19 => Ok(19),
7074        VfpReg::S20 => Ok(20),
7075        VfpReg::S21 => Ok(21),
7076        VfpReg::S22 => Ok(22),
7077        VfpReg::S23 => Ok(23),
7078        VfpReg::S24 => Ok(24),
7079        VfpReg::S25 => Ok(25),
7080        VfpReg::S26 => Ok(26),
7081        VfpReg::S27 => Ok(27),
7082        VfpReg::S28 => Ok(28),
7083        VfpReg::S29 => Ok(29),
7084        VfpReg::S30 => Ok(30),
7085        VfpReg::S31 => Ok(31),
7086        // D-registers are not used in F32 single-precision encodings
7087        _ => Err(synth_core::Error::SynthesisError(
7088            "D-register not supported in single-precision VFP encoding".to_string(),
7089        )),
7090    }
7091}
7092
7093/// D-register number: D0=0, D1=1, ..., D15=15
7094fn vfp_dreg_to_num(reg: &VfpReg) -> Result<u32> {
7095    match reg {
7096        VfpReg::D0 => Ok(0),
7097        VfpReg::D1 => Ok(1),
7098        VfpReg::D2 => Ok(2),
7099        VfpReg::D3 => Ok(3),
7100        VfpReg::D4 => Ok(4),
7101        VfpReg::D5 => Ok(5),
7102        VfpReg::D6 => Ok(6),
7103        VfpReg::D7 => Ok(7),
7104        VfpReg::D8 => Ok(8),
7105        VfpReg::D9 => Ok(9),
7106        VfpReg::D10 => Ok(10),
7107        VfpReg::D11 => Ok(11),
7108        VfpReg::D12 => Ok(12),
7109        VfpReg::D13 => Ok(13),
7110        VfpReg::D14 => Ok(14),
7111        VfpReg::D15 => Ok(15),
7112        // S-registers are not used in F64 double-precision encodings
7113        _ => Err(synth_core::Error::SynthesisError(
7114            "S-register not supported in double-precision VFP encoding".to_string(),
7115        )),
7116    }
7117}
7118
7119/// Split S-register into (Vx[3:0], qualifier_bit) for VFP encoding.
7120/// For an S-register number s: Vx = s >> 1, qualifier = s & 1.
7121/// The qualifier bit goes to D (bit 22), N (bit 7), or M (bit 5) depending on role.
7122fn encode_sreg(s: u32) -> (u32, u32) {
7123    (s >> 1, s & 1)
7124}
7125
7126/// Split D-register into (Vx[3:0], qualifier_bit) for VFP double-precision encoding.
7127/// For a D-register number d: Vx = d & 0xF, qualifier = (d >> 4) & 1.
7128/// For D0-D15, qualifier is always 0.
7129fn encode_dreg(d: u32) -> (u32, u32) {
7130    (d & 0xF, (d >> 4) & 1)
7131}
7132
7133/// Encode a VFP 3-register arithmetic instruction (VADD.F32, VSUB.F32, VMUL.F32, VDIV.F32).
7134/// Returns the full 32-bit instruction word.
7135///
7136/// VFP encoding: [cond 1110] [D opc1 Vn] [Vd 101 sz] [N opc2 M 0 Vm]
7137/// For single-precision (sz=0), coprocessor = 0xA (bits[11:8]).
7138fn encode_vfp_3reg(base: u32, sd: &VfpReg, sn: &VfpReg, sm: &VfpReg) -> Result<u32> {
7139    let sd_num = vfp_sreg_to_num(sd)?;
7140    let sn_num = vfp_sreg_to_num(sn)?;
7141    let sm_num = vfp_sreg_to_num(sm)?;
7142    let (vd, d) = encode_sreg(sd_num);
7143    let (vn, n) = encode_sreg(sn_num);
7144    let (vm, m) = encode_sreg(sm_num);
7145
7146    Ok(base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm)
7147}
7148
7149/// Encode a VFP 2-register instruction (VNEG.F32, VABS.F32, VSQRT.F32).
7150/// Returns the full 32-bit instruction word.
7151fn encode_vfp_2reg(base: u32, sd: &VfpReg, sm: &VfpReg) -> Result<u32> {
7152    let sd_num = vfp_sreg_to_num(sd)?;
7153    let sm_num = vfp_sreg_to_num(sm)?;
7154    let (vd, d) = encode_sreg(sd_num);
7155    let (vm, m) = encode_sreg(sm_num);
7156
7157    Ok(base | (d << 22) | (vd << 12) | (m << 5) | vm)
7158}
7159
7160/// Encode a VFP load/store (VLDR.F32 / VSTR.F32).
7161/// offset is in bytes and must be word-aligned; encoded as imm8 = offset/4.
7162/// U bit (bit 23) controls add/subtract offset.
7163fn encode_vfp_ldst(base: u32, sd: &VfpReg, addr: &MemAddr) -> Result<u32> {
7164    let sd_num = vfp_sreg_to_num(sd)?;
7165    let (vd, d) = encode_sreg(sd_num);
7166    let rn = reg_to_bits(&addr.base);
7167
7168    let offset = addr.offset;
7169    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7170    let abs_offset = offset.unsigned_abs();
7171    let imm8 = (abs_offset / 4) & 0xFF;
7172
7173    Ok(base | (u_bit << 23) | (d << 22) | (rn << 16) | (vd << 12) | imm8)
7174}
7175
7176/// Encode VMOV between core register and S-register.
7177/// VMOV Sn, Rt: 0xEE00_0A10 | (Vn << 16) | (N << 7) | (Rt << 12)
7178/// VMOV Rt, Sn: 0xEE10_0A10 | (Vn << 16) | (N << 7) | (Rt << 12)
7179fn encode_vmov_core_sreg(to_sreg: bool, sreg: &VfpReg, core: &Reg) -> Result<u32> {
7180    let s_num = vfp_sreg_to_num(sreg)?;
7181    let (vn, n) = encode_sreg(s_num);
7182    let rt = reg_to_bits(core);
7183
7184    let base = if to_sreg { 0xEE000A10 } else { 0xEE100A10 };
7185    Ok(base | (vn << 16) | (rt << 12) | (n << 7))
7186}
7187
7188/// Encode a VFP 3-register double-precision instruction (VADD.F64, VSUB.F64, etc.).
7189/// For double-precision (sz=1), coprocessor = 0xB (bits[11:8]).
7190/// The base should have bit 8 = 1 for F64 (0xB suffix instead of 0xA).
7191fn encode_vfp_3reg_f64(base: u32, dd: &VfpReg, dn: &VfpReg, dm: &VfpReg) -> Result<u32> {
7192    let dd_num = vfp_dreg_to_num(dd)?;
7193    let dn_num = vfp_dreg_to_num(dn)?;
7194    let dm_num = vfp_dreg_to_num(dm)?;
7195    let (vd, d) = encode_dreg(dd_num);
7196    let (vn, n) = encode_dreg(dn_num);
7197    let (vm, m) = encode_dreg(dm_num);
7198
7199    Ok(base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm)
7200}
7201
7202/// Encode a VFP 2-register double-precision instruction (VNEG.F64, VABS.F64, VSQRT.F64).
7203fn encode_vfp_2reg_f64(base: u32, dd: &VfpReg, dm: &VfpReg) -> Result<u32> {
7204    let dd_num = vfp_dreg_to_num(dd)?;
7205    let dm_num = vfp_dreg_to_num(dm)?;
7206    let (vd, d) = encode_dreg(dd_num);
7207    let (vm, m) = encode_dreg(dm_num);
7208
7209    Ok(base | (d << 22) | (vd << 12) | (m << 5) | vm)
7210}
7211
7212/// Encode a VFP load/store for double-precision (VLDR.64 / VSTR.64).
7213/// offset is in bytes and must be word-aligned; encoded as imm8 = offset/4.
7214fn encode_vfp_ldst_f64(base: u32, dd: &VfpReg, addr: &MemAddr) -> Result<u32> {
7215    let dd_num = vfp_dreg_to_num(dd)?;
7216    let (vd, d) = encode_dreg(dd_num);
7217    let rn = reg_to_bits(&addr.base);
7218
7219    let offset = addr.offset;
7220    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7221    let abs_offset = offset.unsigned_abs();
7222    let imm8 = (abs_offset / 4) & 0xFF;
7223
7224    Ok(base | (u_bit << 23) | (d << 22) | (rn << 16) | (vd << 12) | imm8)
7225}
7226
7227/// Encode VMOV between two core registers and a D-register.
7228/// VMOV Dm, Rt, Rt2: 0xEC40_0B10 | (Rt2 << 16) | (Rt << 12) | (M << 5) | Vm
7229/// VMOV Rt, Rt2, Dm: 0xEC50_0B10 | (Rt2 << 16) | (Rt << 12) | (M << 5) | Vm
7230fn encode_vmov_core_dreg(
7231    to_dreg: bool,
7232    dreg: &VfpReg,
7233    core_lo: &Reg,
7234    core_hi: &Reg,
7235) -> Result<u32> {
7236    let d_num = vfp_dreg_to_num(dreg)?;
7237    let (vm, m) = encode_dreg(d_num);
7238    let rt = reg_to_bits(core_lo);
7239    let rt2 = reg_to_bits(core_hi);
7240
7241    let base = if to_dreg { 0xEC400B10 } else { 0xEC500B10 };
7242    Ok(base | (rt2 << 16) | (rt << 12) | (m << 5) | vm)
7243}
7244
7245/// Emit a VFP 32-bit instruction as Thumb-2 bytes (two LE halfwords).
7246fn vfp_to_thumb_bytes(instr: u32) -> Vec<u8> {
7247    let hw1 = ((instr >> 16) & 0xFFFF) as u16;
7248    let hw2 = (instr & 0xFFFF) as u16;
7249    let mut bytes = hw1.to_le_bytes().to_vec();
7250    bytes.extend_from_slice(&hw2.to_le_bytes());
7251    bytes
7252}
7253
7254// ============================================================================
7255// Helium MVE encoding helpers
7256// ============================================================================
7257
7258/// Q-register number: Q0=0, Q1=1, ..., Q7=7
7259fn qreg_to_num(reg: &QReg) -> u32 {
7260    match reg {
7261        QReg::Q0 => 0,
7262        QReg::Q1 => 1,
7263        QReg::Q2 => 2,
7264        QReg::Q3 => 3,
7265        QReg::Q4 => 4,
7266        QReg::Q5 => 5,
7267        QReg::Q6 => 6,
7268        QReg::Q7 => 7,
7269    }
7270}
7271
7272/// MVE element size to encoding bits: S8=0b00, S16=0b01, S32=0b10
7273fn mve_size_bits(size: &MveSize) -> u32 {
7274    match size {
7275        MveSize::S8 => 0b00,
7276        MveSize::S16 => 0b01,
7277        MveSize::S32 => 0b10,
7278    }
7279}
7280
7281/// Encode MVE 3-register instruction.
7282/// Q-registers are encoded as D-register pairs: Q0=D0:D1, Q1=D2:D3, etc.
7283/// In NEON/MVE encoding, the Q-register uses D-register number = Qn * 2.
7284fn encode_mve_3reg(base: u32, qd: &QReg, qn: &QReg, qm: &QReg) -> u32 {
7285    let d = qreg_to_num(qd) * 2;
7286    let n = qreg_to_num(qn) * 2;
7287    let m = qreg_to_num(qm) * 2;
7288
7289    // Standard NEON/MVE 3-register encoding:
7290    // D bit (bit 22) = Vd[4], Vd[3:0] = bits [15:12]
7291    // N bit (bit 7)  = Vn[4], Vn[3:0] = bits [19:16]
7292    // M bit (bit 5)  = Vm[4], Vm[3:0] = bits [3:0]
7293    let vd = d & 0xF;
7294    let d_bit = (d >> 4) & 1;
7295    let vn = n & 0xF;
7296    let n_bit = (n >> 4) & 1;
7297    let vm = m & 0xF;
7298    let m_bit = (m >> 4) & 1;
7299
7300    base | (d_bit << 22) | (vn << 16) | (vd << 12) | (n_bit << 7) | (m_bit << 5) | vm
7301}
7302
7303/// Encode MVE 3-register bitwise instruction (VAND, VORR, VEOR, VBIC).
7304fn encode_mve_3reg_bitwise(base: u32, qd: &QReg, qn: &QReg, qm: &QReg) -> u32 {
7305    encode_mve_3reg(base, qd, qn, qm)
7306}
7307
7308/// Encode MVE VLDRW.32 Qd, [Rn, #offset]
7309/// Format: EC9x xxxx - contiguous load, word-sized elements
7310fn encode_mve_vldrw(qd: &QReg, addr: &MemAddr) -> u32 {
7311    let qd_enc = qreg_to_num(qd) * 2;
7312    let rn = reg_to_bits(&addr.base);
7313    let offset = addr.offset;
7314    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7315    let abs_offset = offset.unsigned_abs();
7316    let imm7 = (abs_offset / 4) & 0x7F; // 7-bit word-aligned offset
7317
7318    // VLDRW.32 Qd, [Rn, #imm]: ED10 xx80 variant
7319    0xED100E80
7320        | (u_bit << 23)
7321        | ((qd_enc >> 4) << 22)
7322        | (rn << 16)
7323        | ((qd_enc & 0xF) << 12)
7324        | (imm7 & 0x7F)
7325}
7326
7327/// Encode MVE VSTRW.32 Qd, [Rn, #offset]
7328fn encode_mve_vstrw(qd: &QReg, addr: &MemAddr) -> u32 {
7329    let qd_enc = qreg_to_num(qd) * 2;
7330    let rn = reg_to_bits(&addr.base);
7331    let offset = addr.offset;
7332    let u_bit = if offset >= 0 { 1u32 } else { 0u32 };
7333    let abs_offset = offset.unsigned_abs();
7334    let imm7 = (abs_offset / 4) & 0x7F;
7335
7336    0xED000E80
7337        | (u_bit << 23)
7338        | ((qd_enc >> 4) << 22)
7339        | (rn << 16)
7340        | ((qd_enc & 0xF) << 12)
7341        | (imm7 & 0x7F)
7342}
7343
7344impl ArmEncoder {
7345    /// Encode MVE constant load: MOVW+MOVT+VMOV for each 32-bit word, then assemble Q-register
7346    fn encode_thumb_mve_const(&self, qd: &QReg, bytes: &[u8; 16]) -> Result<Vec<u8>> {
7347        let mut result = Vec::new();
7348        let qd_num = qreg_to_num(qd);
7349
7350        // Load each 32-bit word into R12 (temp) then VMOV into S-register
7351        for i in 0..4 {
7352            let word = u32::from_le_bytes([
7353                bytes[i * 4],
7354                bytes[i * 4 + 1],
7355                bytes[i * 4 + 2],
7356                bytes[i * 4 + 3],
7357            ]);
7358            let lo16 = word & 0xFFFF;
7359            let hi16 = (word >> 16) & 0xFFFF;
7360
7361            // MOVW R12, #lo16
7362            result.extend_from_slice(&self.encode_thumb32_movw_raw(12, lo16)?);
7363            // MOVT R12, #hi16
7364            if hi16 != 0 {
7365                result.extend_from_slice(&self.encode_thumb32_movt_raw(12, hi16)?);
7366            }
7367
7368            // VMOV Sn, R12 where Sn = Qd*4 + i
7369            let s_num = qd_num * 4 + i as u32;
7370            let (vn, n) = encode_sreg(s_num);
7371            let vmov: u32 = 0xEE000A10 | (vn << 16) | (12 << 12) | (n << 7);
7372            result.extend_from_slice(&vfp_to_thumb_bytes(vmov));
7373        }
7374
7375        Ok(result)
7376    }
7377
7378    /// Encode lane-wise f32 binary operation (VDIV, etc.) via S-register extraction
7379    fn encode_thumb_mve_lane_wise_f32_binop(
7380        &self,
7381        qd: &QReg,
7382        qn: &QReg,
7383        qm: &QReg,
7384        vfp_base: u32,
7385    ) -> Result<Vec<u8>> {
7386        let mut result = Vec::new();
7387        let qd_num = qreg_to_num(qd);
7388        let qn_num = qreg_to_num(qn);
7389        let qm_num = qreg_to_num(qm);
7390
7391        // For each lane 0..3: use S-registers directly (Q aliasing)
7392        for i in 0..4u32 {
7393            let sd = qd_num * 4 + i;
7394            let sn = qn_num * 4 + i;
7395            let sm = qm_num * 4 + i;
7396
7397            let (vd, d) = encode_sreg(sd);
7398            let (vn, n) = encode_sreg(sn);
7399            let (vm, m) = encode_sreg(sm);
7400
7401            let instr = vfp_base | (d << 22) | (vn << 16) | (vd << 12) | (n << 7) | (m << 5) | vm;
7402            result.extend_from_slice(&vfp_to_thumb_bytes(instr));
7403        }
7404
7405        Ok(result)
7406    }
7407
7408    /// Encode lane-wise f32 VSQRT via S-register extraction
7409    fn encode_thumb_mve_lane_wise_f32_sqrt(&self, qd: &QReg, qm: &QReg) -> Result<Vec<u8>> {
7410        let mut result = Vec::new();
7411        let qd_num = qreg_to_num(qd);
7412        let qm_num = qreg_to_num(qm);
7413
7414        // VSQRT.F32 base: 0xEEB10AC0
7415        for i in 0..4u32 {
7416            let sd = qd_num * 4 + i;
7417            let sm = qm_num * 4 + i;
7418
7419            let (vd, d) = encode_sreg(sd);
7420            let (vm, m) = encode_sreg(sm);
7421
7422            let instr: u32 = 0xEEB10AC0 | (d << 22) | (vd << 12) | (m << 5) | vm;
7423            result.extend_from_slice(&vfp_to_thumb_bytes(instr));
7424        }
7425
7426        Ok(result)
7427    }
7428}
7429
7430#[cfg(test)]
7431mod tests {
7432    use super::*;
7433
7434    #[test]
7435    fn test_encoder_creation() {
7436        let encoder_arm = ArmEncoder::new_arm32();
7437        assert!(!encoder_arm.thumb_mode);
7438
7439        let encoder_thumb = ArmEncoder::new_thumb2();
7440        assert!(encoder_thumb.thumb_mode);
7441    }
7442
7443    /// #204 WAKE-path regression: `SetCond` materialized 0/1 with the 16-bit
7444    /// `MOVS Rd,#imm` (T1), whose Rd field is 3 bits (R0–R7). For a high Rd
7445    /// (R8–R12) `rd_bits << 8` overflows bit 11, flipping the opcode MOVS→CMP
7446    /// (`0x2c00`), so the boolean was never written — gale's `has_waiter` kept a
7447    /// stale value and the binary-sem WAKE dispatch read garbage. High Rd must
7448    /// use the 32-bit `MOV.W` (T2). Verify the bytes, not the IR.
7449    /// #311: the SAME high-Rd MOVS→CMP transmutation as #204, but in the
7450    /// i64 comparison expansions (I64SetCond / I64SetCondZ) — missed by the
7451    /// #204 hardening. With rd=R8 the boolean died in the flags
7452    /// (`ite eq; cmpeq r0,#1; cmpne r0,#0`), so gale's packed-u64 select
7453    /// read a stale register on silicon. High Rd must take MOV.W / CMP.W.
7454    #[test]
7455    fn test_encode_i64setcond_high_reg_uses_mov_w_311() {
7456        use synth_synthesis::{ArmOp, Condition, Reg};
7457        let enc = ArmEncoder::new_thumb2();
7458        let bytes = enc
7459            .encode(&ArmOp::I64SetCond {
7460                rd: Reg::R8,
7461                rn_lo: Reg::R2,
7462                rn_hi: Reg::R3,
7463                rm_lo: Reg::R6,
7464                rm_hi: Reg::R7,
7465                cond: Condition::EQ,
7466            })
7467            .unwrap();
7468        // The 32-bit MOV.W immediate (T2) first halfword is 0xF04F; the
7469        // 16-bit transmuted forms would contain 0x2801/0x2800 (CMP r0,#1/#0).
7470        let halfwords: Vec<u16> = bytes
7471            .chunks(2)
7472            .map(|c| u16::from_le_bytes([c[0], c[1]]))
7473            .collect();
7474        assert!(
7475            halfwords.iter().filter(|&&h| h == 0xF04F).count() == 2,
7476            "high rd must use two MOV.W (T2) encodings, got {halfwords:04x?}"
7477        );
7478        assert!(
7479            !halfwords.contains(&0x2801) && !halfwords.contains(&0x2800),
7480            "no transmuted 16-bit CMP imm: {halfwords:04x?}"
7481        );
7482
7483        let bytes_z = enc
7484            .encode(&ArmOp::I64SetCondZ {
7485                rd: Reg::R8,
7486                rn_lo: Reg::R2,
7487                rn_hi: Reg::R3,
7488            })
7489            .unwrap();
7490        let hw_z: Vec<u16> = bytes_z
7491            .chunks(2)
7492            .map(|c| u16::from_le_bytes([c[0], c[1]]))
7493            .collect();
7494        assert!(
7495            hw_z.iter().filter(|&&h| h == 0xF04F).count() == 2,
7496            "SetCondZ high rd MOV.W: {hw_z:04x?}"
7497        );
7498        // CMP.W rd,#0 (T2) first halfword: 0xF1B0 | rd
7499        assert!(
7500            hw_z.contains(&(0xF1B0 | 8)),
7501            "SetCondZ high rd must use CMP.W: {hw_z:04x?}"
7502        );
7503    }
7504
7505    #[test]
7506    fn test_encode_setcond_high_reg_uses_mov_w_204() {
7507        use synth_synthesis::{ArmOp, Condition, Reg};
7508        let enc = ArmEncoder::new_thumb2();
7509        // R12 (high): must be ITE + MOV.W #1 + MOV.W #0, never a 16-bit MOVS/CMP.
7510        let hi = enc
7511            .encode(&ArmOp::SetCond {
7512                rd: Reg::R12,
7513                cond: Condition::NE,
7514            })
7515            .unwrap();
7516        assert_eq!(hi.len(), 10, "ITE(2) + MOV.W(4) + MOV.W(4): {hi:02x?}");
7517        // both value halfwords are MOV.W (0xF04F) — NOT the corrupt CMP (0x2c..).
7518        assert_eq!(&hi[2..4], &[0x4F, 0xF0], "then = MOV.W: {hi:02x?}");
7519        assert_eq!(&hi[6..8], &[0x4F, 0xF0], "else = MOV.W: {hi:02x?}");
7520        assert_eq!(hi[4] & 0x0F, 0x01, "then imm = #1");
7521        assert_eq!(hi[8] & 0x0F, 0x00, "else imm = #0");
7522        // Low Rd keeps the compact 16-bit MOVS form.
7523        let lo = enc
7524            .encode(&ArmOp::SetCond {
7525                rd: Reg::R0,
7526                cond: Condition::NE,
7527            })
7528            .unwrap();
7529        assert_eq!(lo.len(), 6, "ITE(2) + MOVS(2) + MOVS(2): {lo:02x?}");
7530        assert_eq!(lo[2..4], [0x01, 0x20], "then = MOVS R0,#1");
7531        assert_eq!(lo[4..6], [0x00, 0x20], "else = MOVS R0,#0");
7532    }
7533
7534    /// #209 Opt 1b: UMULL RdLo, RdHi, Rn, Rm encodes correctly on both ISAs.
7535    /// Thumb-2 T1: 1111 1011 1010 Rn | RdLo RdHi 0000 Rm.
7536    /// A32:        cond 0000 1000 RdHi RdLo Rm 1001 Rn.
7537    #[test]
7538    fn test_encode_umull_209b() {
7539        use synth_synthesis::{ArmOp, Reg};
7540        let op = ArmOp::Umull {
7541            rdlo: Reg::R4,
7542            rdhi: Reg::R5,
7543            rn: Reg::R0,
7544            rm: Reg::R3,
7545        };
7546        // Thumb-2: hw1 = 0xFBA0 | 0 = 0xFBA0; hw2 = (4<<12)|(5<<8)|3 = 0x4503.
7547        let t = ArmEncoder::new_thumb2().encode(&op).unwrap();
7548        assert_eq!(
7549            t,
7550            vec![0xA0, 0xFB, 0x03, 0x45],
7551            "umull r4,r5,r0,r3 (T2): {t:02x?}"
7552        );
7553        // A32: 0xE0800090 | (5<<16) | (4<<12) | (3<<8) | 0 = 0xE0854390.
7554        let a = ArmEncoder::new_arm32().encode(&op).unwrap();
7555        assert_eq!(
7556            a,
7557            0xE085_4390u32.to_le_bytes().to_vec(),
7558            "umull (A32): {a:02x?}"
7559        );
7560    }
7561
7562    /// #206 regression: the ARM32 (A32) `Ldr`/`Str` encoders fed `addr` through
7563    /// `encode_mem_addr`, which returns only the 12-bit immediate — so a register
7564    /// offset (`[rn, rm, #off]`) was silently dropped to `[rn, #off]`, sending
7565    /// the access to the wrong runtime address (silent miscompile on the default
7566    /// `--target arm`). A register offset must materialize `ip = rn + rm` and
7567    /// load from `[ip, #off]`. Verify the bytes.
7568    #[test]
7569    fn test_encode_arm32_indexed_load_keeps_index_206() {
7570        use synth_synthesis::{ArmOp, MemAddr, Reg};
7571        let enc = ArmEncoder::new_arm32();
7572        // ldr r0, [r11, r1, #8]  must NOT collapse to a single immediate ldr.
7573        let bytes = enc
7574            .encode(&ArmOp::Ldr {
7575                rd: Reg::R0,
7576                addr: MemAddr::reg_imm(Reg::R11, Reg::R1, 8),
7577            })
7578            .unwrap();
7579        assert_eq!(
7580            bytes.len(),
7581            8,
7582            "expected ADD ip + LDR (2 words): {bytes:02x?}"
7583        );
7584        let add = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
7585        let ldr = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
7586        // ADD ip, r11, r1  = 0xE08BC001
7587        assert_eq!(add, 0xE08B_C001, "ADD ip,r11,r1: {add:#010x}");
7588        // LDR r0, [ip, #8] = 0xE59C0008
7589        assert_eq!(ldr, 0xE59C_0008, "LDR r0,[ip,#8]: {ldr:#010x}");
7590        // A bare immediate ldr (the bug) would be 0xE59B0008 (base=r11) — reject.
7591        assert_ne!(ldr, 0xE59B_0008, "index must not be dropped");
7592    }
7593
7594    /// #594 regression: `call_indirect` on the A32 path (`--target cortex-r5`)
7595    /// was encoded as a literal NOP (0xE1A00000) — the call never happened and
7596    /// the function silently returned the leftover table-index value. The A32
7597    /// encoder must emit the same three-instruction expansion as Thumb-2:
7598    /// `MOV r12, idx, LSL #2; LDR r12, [r11, r12]; BLX r12`.
7599    #[test]
7600    fn test_encode_arm32_call_indirect_is_real_call_594() {
7601        use synth_synthesis::{ArmOp, Reg};
7602        let enc = ArmEncoder::new_arm32();
7603        let bytes = enc
7604            .encode(&ArmOp::CallIndirect {
7605                rd: Reg::R0,
7606                type_idx: 0,
7607                table_index_reg: Reg::R0,
7608            })
7609            .unwrap();
7610        assert_eq!(
7611            bytes.len(),
7612            12,
7613            "expected MOV + LDR + BLX (3 words): {bytes:02x?}"
7614        );
7615        let mov = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
7616        let ldr = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
7617        let blx = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
7618        // MOV r12, r0, LSL #2 = 0xE1A0C100
7619        assert_eq!(mov, 0xE1A0_C100, "MOV r12,r0,LSL#2: {mov:#010x}");
7620        // LDR r12, [r11, r12] = 0xE79BC00C
7621        assert_eq!(ldr, 0xE79B_C00C, "LDR r12,[r11,r12]: {ldr:#010x}");
7622        // BLX r12 = 0xE12FFF3C
7623        assert_eq!(blx, 0xE12F_FF3C, "BLX r12: {blx:#010x}");
7624        // The bug: a single NOP word. Must never come back.
7625        assert!(
7626            !bytes
7627                .chunks_exact(4)
7628                .any(|w| w == 0xE1A0_0000u32.to_le_bytes()),
7629            "call_indirect must not contain a NOP (#594): {bytes:02x?}"
7630        );
7631
7632        // A non-R0 index register lands in the MOV's Rm field.
7633        let bytes = enc
7634            .encode(&ArmOp::CallIndirect {
7635                rd: Reg::R0,
7636                type_idx: 0,
7637                table_index_reg: Reg::R4,
7638            })
7639            .unwrap();
7640        let mov = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
7641        assert_eq!(mov, 0xE1A0_C104, "MOV r12,r4,LSL#2: {mov:#010x}");
7642    }
7643
7644    /// #597 anchor (justified correctness RE-PIN of the #594-era freeze): the
7645    /// Thumb-2 `CallIndirect` expansion is `mov.w ip, rm, LSL #2; ldr.w ip,
7646    /// [r11, ip]; blx ip`.
7647    ///
7648    /// The #594 PR froze the then-current bytes `4F EA 20 0C ...` whose first
7649    /// word decodes as `mov.w ip, rm, ASR #32` — the intended `LSL #2` had
7650    /// its shift amount in the TYPE field (bits 5:4) instead of imm2 (bits
7651    /// 7:6), so the index was destroyed and every call_indirect dispatched
7652    /// table entry 0 (shipped miscompile, masked by index-0 probes). #597
7653    /// corrects the encoding; new bytes `4F EA 80 0C ...` were
7654    /// execution-validated under unicorn against the wasmtime oracle on a
7655    /// multi-entry table (indexes 0, 1, 3 —
7656    /// scripts/repro/call_indirect_597_differential.py) before this pin was
7657    /// replaced. Old pin: [4F EA 20 0C, 5B F8 0C C0, E0 47] (ASR #32 — must
7658    /// never come back).
7659    #[test]
7660    fn test_encode_thumb_call_indirect_lsl2_597() {
7661        use synth_synthesis::{ArmOp, Reg};
7662        let enc = ArmEncoder::new_thumb2();
7663        let bytes = enc
7664            .encode(&ArmOp::CallIndirect {
7665                rd: Reg::R0,
7666                type_idx: 0,
7667                table_index_reg: Reg::R0,
7668            })
7669            .unwrap();
7670        assert_eq!(
7671            bytes,
7672            vec![0x4F, 0xEA, 0x80, 0x0C, 0x5B, 0xF8, 0x0C, 0xC0, 0xE0, 0x47],
7673            "Thumb-2 CallIndirect: mov.w ip,r0,LSL#2; ldr.w ip,[r11,ip]; blx ip: {bytes:02x?}"
7674        );
7675        // The #597 bug bytes (ASR #32 first word) must never come back.
7676        assert_ne!(
7677            &bytes[0..4],
7678            &[0x4F, 0xEA, 0x20, 0x0C],
7679            "mov.w ip, rm, ASR #32 — the #597 type-field bug"
7680        );
7681
7682        // A non-R0 index register lands in the mov.w's Rm field (hw2 bits 3:0).
7683        let bytes = enc
7684            .encode(&ArmOp::CallIndirect {
7685                rd: Reg::R0,
7686                type_idx: 0,
7687                table_index_reg: Reg::R4,
7688            })
7689            .unwrap();
7690        assert_eq!(
7691            &bytes[0..4],
7692            &[0x4F, 0xEA, 0x84, 0x0C],
7693            "mov.w ip, r4, LSL #2: {bytes:02x?}"
7694        );
7695    }
7696
7697    /// #178/#180 regression: the Thumb `Add`/`Adds`/`Subs` reg-forms used the
7698    /// 16-bit encoding unconditionally. For high registers (R12 base scratch,
7699    /// R8-R11 i64 pairs) the 3-bit register fields overflow and corrupt the
7700    /// operands — `add ip,ip,r0` came out as `adds r4,r5,r1` (0x186C), silently
7701    /// dropping the address operand and miscompiling every optimized memory
7702    /// access. High registers must use the 32-bit `.W` forms.
7703    #[test]
7704    fn test_encode_thumb_add_high_reg_uses_add_w_178_180() {
7705        let encoder = ArmEncoder::new_thumb2();
7706
7707        // add ip, ip, r0  — the exact MemLoad/MemStore base+addr op.
7708        let code = encoder
7709            .encode(&ArmOp::Add {
7710                rd: Reg::R12,
7711                rn: Reg::R12,
7712                op2: Operand2::Reg(Reg::R0),
7713            })
7714            .unwrap();
7715        // ADD.W ip, ip, r0 = EB0C 0C00 (little-endian halfwords).
7716        assert_eq!(
7717            code,
7718            vec![0x0C, 0xEB, 0x00, 0x0C],
7719            "high-reg Thumb ADD must be 32-bit ADD.W (EB0C 0C00), not corrupt 16-bit; got {code:02X?}"
7720        );
7721        // Must NOT be the buggy 16-bit 0x186C (`adds r4,r5,r1`).
7722        assert_ne!(code, vec![0x6C, 0x18], "regressed to corrupt 16-bit ADDS");
7723
7724        // Low-register add stays 16-bit (no regression for the common case).
7725        let lo = encoder
7726            .encode(&ArmOp::Add {
7727                rd: Reg::R1,
7728                rn: Reg::R2,
7729                op2: Operand2::Reg(Reg::R3),
7730            })
7731            .unwrap();
7732        assert_eq!(
7733            lo.len(),
7734            2,
7735            "low-reg ADD should remain 16-bit, got {lo:02X?}"
7736        );
7737    }
7738
7739    /// #178/#180 sibling: i64 low-word `Adds`/`Subs` can land in R8-R11 pairs;
7740    /// those must fall back to 32-bit ADDS.W/SUBS.W (flag-setting preserved).
7741    #[test]
7742    fn test_encode_thumb_adds_subs_high_reg_use_32bit_178_180() {
7743        let encoder = ArmEncoder::new_thumb2();
7744
7745        // adds r10, r10, r8  → ADDS.W = EB1A 0A08
7746        let adds = encoder
7747            .encode(&ArmOp::Adds {
7748                rd: Reg::R10,
7749                rn: Reg::R10,
7750                op2: Operand2::Reg(Reg::R8),
7751            })
7752            .unwrap();
7753        assert_eq!(
7754            adds,
7755            vec![0x1A, 0xEB, 0x08, 0x0A],
7756            "high-reg ADDS must be 32-bit ADDS.W (EB1A 0A08); got {adds:02X?}"
7757        );
7758
7759        // subs r10, r10, r8  → SUBS.W = EBBA 0A08
7760        let subs = encoder
7761            .encode(&ArmOp::Subs {
7762                rd: Reg::R10,
7763                rn: Reg::R10,
7764                op2: Operand2::Reg(Reg::R8),
7765            })
7766            .unwrap();
7767        assert_eq!(
7768            subs,
7769            vec![0xBA, 0xEB, 0x08, 0x0A],
7770            "high-reg SUBS must be 32-bit SUBS.W (EBBA 0A08); got {subs:02X?}"
7771        );
7772    }
7773
7774    /// #184 (sibling of #180): 16-bit CMN (T1) only encodes R0-R7. High registers
7775    /// must use 32-bit CMN.W, not the corrupt truncated 16-bit form.
7776    #[test]
7777    fn test_encode_thumb_cmn_high_reg_uses_cmn_w_184() {
7778        let encoder = ArmEncoder::new_thumb2();
7779
7780        // cmn r10, r8  → CMN.W = EB1A 0F08 (ADD.W S=1, Rd=PC discarded).
7781        let cmn = encoder
7782            .encode(&ArmOp::Cmn {
7783                rn: Reg::R10,
7784                op2: Operand2::Reg(Reg::R8),
7785            })
7786            .unwrap();
7787        assert_eq!(
7788            cmn,
7789            vec![0x1A, 0xEB, 0x08, 0x0F],
7790            "high-reg CMN must be 32-bit CMN.W (EB1A 0F08); got {cmn:02X?}"
7791        );
7792
7793        // Low registers stay 16-bit: cmn r1, r2 = 0x42D1.
7794        let lo = encoder
7795            .encode(&ArmOp::Cmn {
7796                rn: Reg::R1,
7797                op2: Operand2::Reg(Reg::R2),
7798            })
7799            .unwrap();
7800        assert_eq!(
7801            lo.len(),
7802            2,
7803            "low-reg CMN should remain 16-bit, got {lo:02X?}"
7804        );
7805        assert_eq!(lo, vec![0xD1, 0x42], "low-reg CMN bytes wrong: {lo:02X?}");
7806    }
7807
7808    /// #185 regression: feeding PC (R15) as a data operand to a Thumb-2 op that
7809    /// guards its registers must return Err, not panic under debug-assertions.
7810    /// (Synth never emits PC here; the fuzz harness requires encode() be total.)
7811    #[test]
7812    fn test_encode_pc_operand_returns_err_not_panic_185() {
7813        let encoder = ArmEncoder::new_thumb2();
7814        for op in [
7815            ArmOp::Sdiv {
7816                rd: Reg::PC,
7817                rn: Reg::R0,
7818                rm: Reg::R1,
7819            },
7820            ArmOp::Udiv {
7821                rd: Reg::R0,
7822                rn: Reg::PC,
7823                rm: Reg::R1,
7824            },
7825            ArmOp::Sdiv {
7826                rd: Reg::R0,
7827                rn: Reg::R1,
7828                rm: Reg::PC,
7829            },
7830        ] {
7831            let r = encoder.encode(&op);
7832            assert!(
7833                r.is_err(),
7834                "encode({op:?}) must return Err for a PC operand, got {r:?}"
7835            );
7836        }
7837        // Valid registers still encode fine (no false rejection).
7838        assert!(
7839            encoder
7840                .encode(&ArmOp::Sdiv {
7841                    rd: Reg::R0,
7842                    rn: Reg::R1,
7843                    rm: Reg::R2
7844                })
7845                .is_ok()
7846        );
7847    }
7848
7849    #[test]
7850    fn test_encode_nop_arm32() {
7851        let encoder = ArmEncoder::new_arm32();
7852        let code = encoder.encode(&ArmOp::Nop).unwrap();
7853
7854        assert_eq!(code.len(), 4); // ARM32 instructions are 4 bytes
7855        assert_eq!(code, vec![0x00, 0x00, 0xA0, 0xE1]); // MOV R0, R0
7856    }
7857
7858    #[test]
7859    fn test_encode_nop_thumb() {
7860        let encoder = ArmEncoder::new_thumb2();
7861        let code = encoder.encode(&ArmOp::Nop).unwrap();
7862
7863        assert_eq!(code.len(), 2); // Thumb instructions are 2 bytes
7864        assert_eq!(code, vec![0x00, 0xBF]); // NOP
7865    }
7866
7867    #[test]
7868    fn test_encode_mov_immediate_arm32() {
7869        let encoder = ArmEncoder::new_arm32();
7870        let op = ArmOp::Mov {
7871            rd: Reg::R0,
7872            op2: Operand2::Imm(42),
7873        };
7874
7875        let code = encoder.encode(&op).unwrap();
7876        assert_eq!(code.len(), 4);
7877
7878        // Verify it's a MOV instruction (bits should have immediate flag set)
7879        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7880        assert_eq!(instr & 0x0E000000, 0x02000000); // Check I bit is set
7881    }
7882
7883    #[test]
7884    fn test_encode_add_registers_arm32() {
7885        let encoder = ArmEncoder::new_arm32();
7886        let op = ArmOp::Add {
7887            rd: Reg::R0,
7888            rn: Reg::R1,
7889            op2: Operand2::Reg(Reg::R2),
7890        };
7891
7892        let code = encoder.encode(&op).unwrap();
7893        assert_eq!(code.len(), 4);
7894
7895        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
7896        // Verify it's an ADD instruction with correct opcode
7897        assert_eq!(instr & 0x0FE00000, 0x00800000);
7898    }
7899
7900    /// #350 — `encode_thumb32_add_imm` must lower an out-of-range immediate
7901    /// (> 0xFFF) to a legal MOVW(/MOVT) + ADD.W-register sequence instead of
7902    /// erroring. The small-imm fast path (imm <= 0xFFF) stays byte-identical.
7903    #[test]
7904    fn test_encode_add_imm_large_350() {
7905        let enc = ArmEncoder::new_thumb2();
7906
7907        // --- Fast path unchanged: imm <= 0xFFF is a single 4-byte ADD.W ---
7908        let small = enc
7909            .encode_thumb32_add_imm(&Reg::R0, &Reg::R1, 0x123)
7910            .unwrap();
7911        assert_eq!(small.len(), 4, "small imm must stay a single instruction");
7912
7913        // helper: decode a Thumb-2 MOVW/MOVT halfword pair back to its imm16
7914        fn movx_imm16(b: &[u8]) -> u32 {
7915            let hw1 = u16::from_le_bytes([b[0], b[1]]) as u32;
7916            let hw2 = u16::from_le_bytes([b[2], b[3]]) as u32;
7917            let imm4 = hw1 & 0xF;
7918            let i = (hw1 >> 10) & 1;
7919            let imm3 = (hw2 >> 12) & 0x7;
7920            let imm8 = hw2 & 0xFF;
7921            (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8
7922        }
7923        fn movx_rd(b: &[u8]) -> u32 {
7924            (u16::from_le_bytes([b[2], b[3]]) as u32 >> 8) & 0xF
7925        }
7926
7927        // --- rd != rn: scratch is rd. imm = 70000 = 0x11170 needs MOVW+MOVT. ---
7928        // 0x11170: lo16 = 0x1170, hi16 = 0x0001
7929        let seq = enc
7930            .encode_thumb32_add_imm(&Reg::R12, &Reg::R0, 70000)
7931            .unwrap();
7932        assert_eq!(seq.len(), 12, "MOVW + MOVT + ADD = 12 bytes");
7933        // MOVW r12, #0x1170
7934        assert_eq!(u16::from_le_bytes([seq[0], seq[1]]) & 0xFBF0, 0xF240);
7935        assert_eq!(movx_rd(&seq[0..4]), 12);
7936        assert_eq!(movx_imm16(&seq[0..4]), 0x1170);
7937        // MOVT r12, #0x0001
7938        assert_eq!(u16::from_le_bytes([seq[4], seq[5]]) & 0xFBF0, 0xF2C0);
7939        assert_eq!(movx_rd(&seq[4..8]), 12);
7940        assert_eq!(movx_imm16(&seq[4..8]), 0x0001);
7941        // ADD.W r12, r0, r12  (EB00 | rn=0 ; rd=12, rm=12)
7942        let add1 = u16::from_le_bytes([seq[8], seq[9]]) as u32;
7943        let add2 = u16::from_le_bytes([seq[10], seq[11]]) as u32;
7944        assert_eq!(add1 & 0xFFF0, 0xEB00);
7945        assert_eq!(add1 & 0xF, 0); // rn = r0
7946        assert_eq!((add2 >> 8) & 0xF, 12); // rd = r12
7947        assert_eq!(add2 & 0xF, 12); // rm = scratch = r12
7948        // The materialized scratch must reconstruct exactly 70000.
7949        assert_eq!(
7950            (movx_imm16(&seq[4..8]) << 16) | movx_imm16(&seq[0..4]),
7951            70000
7952        );
7953
7954        // --- imm <= 0xFFFF: MOVT is skipped (MOVW + ADD = 8 bytes). ---
7955        let seq16 = enc
7956            .encode_thumb32_add_imm(&Reg::R3, &Reg::R0, 0xABCD)
7957            .unwrap();
7958        assert_eq!(seq16.len(), 8, "imm <= 0xFFFF skips MOVT");
7959        assert_eq!(movx_imm16(&seq16[0..4]), 0xABCD);
7960        assert_eq!(movx_rd(&seq16[0..4]), 3); // scratch = rd = r3
7961
7962        // --- rd == rn (in-place add): scratch must be R12, not rd. ---
7963        // imm = 0x12345: lo16 = 0x2345, hi16 = 0x0001
7964        let inplace = enc
7965            .encode_thumb32_add_imm(&Reg::R5, &Reg::R5, 0x12345)
7966            .unwrap();
7967        assert_eq!(inplace.len(), 12);
7968        assert_eq!(movx_rd(&inplace[0..4]), 12, "rd==rn must use R12 scratch");
7969        assert_eq!(
7970            (movx_imm16(&inplace[4..8]) << 16) | movx_imm16(&inplace[0..4]),
7971            0x12345
7972        );
7973        // ADD.W r5, r5, r12 — rm must be the scratch (12), never rn.
7974        let ip_add2 = u16::from_le_bytes([inplace[10], inplace[11]]) as u32;
7975        assert_eq!(ip_add2 & 0xF, 12);
7976        assert_eq!((ip_add2 >> 8) & 0xF, 5);
7977    }
7978
7979    /// #350 follow-up — the `encoder_no_panic` fuzz harness drives the encoder
7980    /// with ARBITRARY registers, including the one case the in-place lowering
7981    /// cannot serve: rd==rn==R12. There the scratch (R12, the reserved encoder
7982    /// register) would alias Rn and clobber it before the ADD reads it. The
7983    /// encoder contract (#180/#185) is Ok-or-Err, never a panic — so this must
7984    /// return Err, not assert. (Real codegen never emits rd==rn==R12 because R12
7985    /// is non-allocatable; this guards only the fuzz/adversarial path.)
7986    #[test]
7987    fn test_encode_add_imm_large_rd_rn_r12_errs_not_panics_350() {
7988        let enc = ArmEncoder::new_thumb2();
7989        // Out-of-range imm with rd==rn==R12: no free scratch -> Err.
7990        let r = enc.encode_thumb32_add_imm(&Reg::R12, &Reg::R12, 70000);
7991        assert!(
7992            r.is_err(),
7993            "rd==rn==R12 with out-of-range imm must Err (no free scratch), got {r:?}"
7994        );
7995        // Small imm with rd==rn==R12 still takes the single-instruction fast path
7996        // (no scratch needed) and must succeed — the guard is scoped to the
7997        // out-of-range lowering only.
7998        let small = enc.encode_thumb32_add_imm(&Reg::R12, &Reg::R12, 0x10);
7999        assert!(small.is_ok(), "small imm needs no scratch, must stay Ok");
8000    }
8001
8002    /// #378 — `encode_operand2` (ARM32 data-processing operand) must FAIL
8003    /// HONESTLY on an immediate that is not a valid rotated immediate, rather
8004    /// than silently masking it to `imm & 0xFF` and emitting a WRONG
8005    /// instruction. `0x1FF` has 9 set bits, so it cannot come from rotating an
8006    /// 8-bit imm8 — non-encodable. Real codegen materializes large constants via
8007    /// MOVW/MOVT; this guards the encoder's Ok-or-Err contract (#180/#185)
8008    /// directly. It is an Err (not a panic) so the `encoder_no_panic` fuzz
8009    /// harness — which drives arbitrary operands — still passes.
8010    #[test]
8011    fn test_encode_operand2_non_rotatable_imm_errs_not_masks_378() {
8012        let enc = ArmEncoder::new_arm32();
8013        let bad = enc.encode(&ArmOp::Add {
8014            rd: Reg::R0,
8015            rn: Reg::R1,
8016            op2: Operand2::Imm(0x1FF),
8017        });
8018        assert!(
8019            bad.is_err(),
8020            "non-rotatable ARM32 immediate 0x1FF must Err (was silently masked \
8021             to 0xFF), got {bad:?}"
8022        );
8023        // A representable rotated immediate still encodes fine (regression guard).
8024        let ok = enc.encode(&ArmOp::Add {
8025            rd: Reg::R0,
8026            rn: Reg::R1,
8027            op2: Operand2::Imm(0xFF),
8028        });
8029        assert!(
8030            ok.is_ok(),
8031            "0xFF is a valid rotated immediate, must stay Ok"
8032        );
8033    }
8034
8035    #[test]
8036    fn test_encode_ldr_arm32() {
8037        let encoder = ArmEncoder::new_arm32();
8038        let op = ArmOp::Ldr {
8039            rd: Reg::R0,
8040            addr: MemAddr::imm(Reg::R1, 4),
8041        };
8042
8043        let code = encoder.encode(&op).unwrap();
8044        assert_eq!(code.len(), 4);
8045
8046        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8047        // Verify load bit is set
8048        assert_eq!(instr & 0x00100000, 0x00100000);
8049    }
8050
8051    #[test]
8052    fn test_encode_str_arm32() {
8053        let encoder = ArmEncoder::new_arm32();
8054        let op = ArmOp::Str {
8055            rd: Reg::R0,
8056            addr: MemAddr::imm(Reg::SP, 0),
8057        };
8058
8059        let code = encoder.encode(&op).unwrap();
8060        assert_eq!(code.len(), 4);
8061    }
8062
8063    #[test]
8064    fn test_encode_branch_arm32() {
8065        let encoder = ArmEncoder::new_arm32();
8066        let op = ArmOp::Bl {
8067            label: "main".to_string(),
8068        };
8069
8070        let code = encoder.encode(&op).unwrap();
8071        assert_eq!(code.len(), 4);
8072
8073        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8074        // Verify BL opcode
8075        assert_eq!(instr & 0x0F000000, 0x0B000000);
8076    }
8077
8078    /// Regression test for #167 + #174: the Thumb-2 BL relocatable placeholder
8079    /// must carry a -4 addend so an R_ARM_THM_CALL nets to exactly the symbol S.
8080    /// The correct encoding is what `gas` emits for `bl <extern>`: f7ff fffe
8081    /// (hw1=0xF7FF, hw2=0xFFFE), little-endian bytes FF F7 FE FF.
8082    ///   - 0xD000 (J1=J2=0) → ~+0x600000 garbage addend: `bl c0000c` / truncated
8083    ///     to fit (#167).
8084    ///   - 0xF800 (addend 0) → lands at S+4, one instruction past the callee
8085    ///     entry (#174).
8086    ///   - 0xFFFE (addend -4) → lands at S. Correct.
8087    #[test]
8088    fn test_encode_thumb_bl_placeholder_addend_167_174() {
8089        let encoder = ArmEncoder::new_thumb2();
8090        let op = ArmOp::Bl {
8091            label: "callee".to_string(),
8092        };
8093
8094        let code = encoder.encode(&op).unwrap();
8095        assert_eq!(code.len(), 4, "Thumb-2 BL is 32-bit");
8096
8097        let hw1 = u16::from_le_bytes([code[0], code[1]]);
8098        let hw2 = u16::from_le_bytes([code[2], code[3]]);
8099        assert_eq!(hw1, 0xF7FF, "BL first halfword (matches gas `bl <extern>`)");
8100        assert_eq!(
8101            hw2, 0xFFFE,
8102            "BL second halfword must be 0xFFFE (-4 addend → nets to S), not 0xF800 (→ S+4, #174) or 0xD000 (#167)"
8103        );
8104        assert_ne!(hw2, 0xF800, "0xF800 (addend 0) lands at S+4 (#174)");
8105        assert_ne!(hw2, 0xD000, "0xD000 bakes in a ~+0x600000 addend (#167)");
8106    }
8107
8108    #[test]
8109    fn test_encode_sequence() {
8110        let encoder = ArmEncoder::new_arm32();
8111        let ops = vec![
8112            ArmOp::Mov {
8113                rd: Reg::R0,
8114                op2: Operand2::Imm(42),
8115            },
8116            ArmOp::Mov {
8117                rd: Reg::R1,
8118                op2: Operand2::Imm(10),
8119            },
8120            ArmOp::Add {
8121                rd: Reg::R2,
8122                rn: Reg::R0,
8123                op2: Operand2::Reg(Reg::R1),
8124            },
8125        ];
8126
8127        let code = encoder.encode_sequence(&ops).unwrap();
8128        assert_eq!(code.len(), 12); // 3 instructions * 4 bytes
8129    }
8130
8131    #[test]
8132    fn test_reg_to_bits() {
8133        assert_eq!(reg_to_bits(&Reg::R0), 0);
8134        assert_eq!(reg_to_bits(&Reg::R7), 7);
8135        assert_eq!(reg_to_bits(&Reg::SP), 13);
8136        assert_eq!(reg_to_bits(&Reg::LR), 14);
8137        assert_eq!(reg_to_bits(&Reg::PC), 15);
8138    }
8139
8140    #[test]
8141    fn test_encode_bitwise_operations() {
8142        let encoder = ArmEncoder::new_arm32();
8143
8144        let and_op = ArmOp::And {
8145            rd: Reg::R0,
8146            rn: Reg::R1,
8147            op2: Operand2::Reg(Reg::R2),
8148        };
8149        let and_code = encoder.encode(&and_op).unwrap();
8150        assert_eq!(and_code.len(), 4);
8151
8152        let orr_op = ArmOp::Orr {
8153            rd: Reg::R0,
8154            rn: Reg::R1,
8155            op2: Operand2::Reg(Reg::R2),
8156        };
8157        let orr_code = encoder.encode(&orr_op).unwrap();
8158        assert_eq!(orr_code.len(), 4);
8159
8160        let eor_op = ArmOp::Eor {
8161            rd: Reg::R0,
8162            rn: Reg::R1,
8163            op2: Operand2::Reg(Reg::R2),
8164        };
8165        let eor_code = encoder.encode(&eor_op).unwrap();
8166        assert_eq!(eor_code.len(), 4);
8167    }
8168
8169    // === Thumb-2 32-bit encoding tests ===
8170
8171    #[test]
8172    fn test_encode_sdiv_thumb2() {
8173        let encoder = ArmEncoder::new_thumb2();
8174        let op = ArmOp::Sdiv {
8175            rd: Reg::R0,
8176            rn: Reg::R1,
8177            rm: Reg::R2,
8178        };
8179
8180        let code = encoder.encode(&op).unwrap();
8181        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
8182
8183        // SDIV R0, R1, R2: 0xFB91 0xF0F2
8184        // First halfword: 0xFB90 | Rn(1) = 0xFB91
8185        // Second halfword: 0xF0F0 | Rd(0)<<8 | Rm(2) = 0xF0F2
8186        // Little-endian: [0x91, 0xFB, 0xF2, 0xF0]
8187        assert_eq!(code[0], 0x91);
8188        assert_eq!(code[1], 0xFB);
8189        assert_eq!(code[2], 0xF2);
8190        assert_eq!(code[3], 0xF0);
8191    }
8192
8193    #[test]
8194    fn test_encode_udiv_thumb2() {
8195        let encoder = ArmEncoder::new_thumb2();
8196        let op = ArmOp::Udiv {
8197            rd: Reg::R0,
8198            rn: Reg::R1,
8199            rm: Reg::R2,
8200        };
8201
8202        let code = encoder.encode(&op).unwrap();
8203        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
8204
8205        // UDIV R0, R1, R2: 0xFBB1 0xF0F2
8206        // Little-endian: [0xB1, 0xFB, 0xF2, 0xF0]
8207        assert_eq!(code[0], 0xB1);
8208        assert_eq!(code[1], 0xFB);
8209        assert_eq!(code[2], 0xF2);
8210        assert_eq!(code[3], 0xF0);
8211    }
8212
8213    #[test]
8214    fn test_encode_mul_thumb2() {
8215        let encoder = ArmEncoder::new_thumb2();
8216        let op = ArmOp::Mul {
8217            rd: Reg::R0,
8218            rn: Reg::R1,
8219            rm: Reg::R2,
8220        };
8221
8222        let code = encoder.encode(&op).unwrap();
8223        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
8224    }
8225
8226    #[test]
8227    fn test_encode_and_thumb2() {
8228        let encoder = ArmEncoder::new_thumb2();
8229        let op = ArmOp::And {
8230            rd: Reg::R0,
8231            rn: Reg::R1,
8232            op2: Operand2::Reg(Reg::R2),
8233        };
8234
8235        let code = encoder.encode(&op).unwrap();
8236        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
8237    }
8238
8239    #[test]
8240    fn test_encode_lsl_thumb2_low_regs() {
8241        let encoder = ArmEncoder::new_thumb2();
8242        let op = ArmOp::Lsl {
8243            rd: Reg::R0,
8244            rn: Reg::R1,
8245            shift: 5,
8246        };
8247
8248        let code = encoder.encode(&op).unwrap();
8249        assert_eq!(code.len(), 2); // 16-bit for low registers
8250    }
8251
8252    #[test]
8253    fn test_encode_clz_thumb2() {
8254        let encoder = ArmEncoder::new_thumb2();
8255        let op = ArmOp::Clz {
8256            rd: Reg::R0,
8257            rm: Reg::R1,
8258        };
8259
8260        let code = encoder.encode(&op).unwrap();
8261        assert_eq!(code.len(), 4); // 32-bit Thumb-2 instruction
8262    }
8263
8264    #[test]
8265    fn test_encode_bx_thumb2() {
8266        let encoder = ArmEncoder::new_thumb2();
8267        let op = ArmOp::Bx { rm: Reg::LR };
8268
8269        let code = encoder.encode(&op).unwrap();
8270        assert_eq!(code.len(), 2); // 16-bit instruction
8271
8272        // BX LR: 0x4770
8273        assert_eq!(code, vec![0x70, 0x47]);
8274    }
8275
8276    // ========================================================================
8277    // f32 pseudo-op encoding tests
8278    // ========================================================================
8279
8280    #[test]
8281    fn test_encode_f32_abs_arm32() {
8282        let encoder = ArmEncoder::new_arm32();
8283        let op = ArmOp::F32Abs {
8284            sd: VfpReg::S0,
8285            sm: VfpReg::S2,
8286        };
8287        let code = encoder.encode(&op).unwrap();
8288        assert_eq!(code.len(), 4); // Single VFP instruction
8289    }
8290
8291    #[test]
8292    fn test_encode_f32_neg_arm32() {
8293        let encoder = ArmEncoder::new_arm32();
8294        let op = ArmOp::F32Neg {
8295            sd: VfpReg::S0,
8296            sm: VfpReg::S2,
8297        };
8298        let code = encoder.encode(&op).unwrap();
8299        assert_eq!(code.len(), 4);
8300    }
8301
8302    #[test]
8303    fn test_encode_f32_sqrt_arm32() {
8304        let encoder = ArmEncoder::new_arm32();
8305        let op = ArmOp::F32Sqrt {
8306            sd: VfpReg::S0,
8307            sm: VfpReg::S2,
8308        };
8309        let code = encoder.encode(&op).unwrap();
8310        assert_eq!(code.len(), 4);
8311    }
8312
8313    #[test]
8314    fn test_encode_f32_ceil_arm32() {
8315        let encoder = ArmEncoder::new_arm32();
8316        let op = ArmOp::F32Ceil {
8317            sd: VfpReg::S0,
8318            sm: VfpReg::S2,
8319        };
8320        let code = encoder.encode(&op).unwrap();
8321        // VMRS + BIC + ORR + VMSR + VCVT.S32.F32 + VMRS + BIC + VMSR + VCVT.F32.S32
8322        assert_eq!(code.len(), 36);
8323    }
8324
8325    #[test]
8326    fn test_encode_f32_floor_thumb2() {
8327        let encoder = ArmEncoder::new_thumb2();
8328        let op = ArmOp::F32Floor {
8329            sd: VfpReg::S0,
8330            sm: VfpReg::S2,
8331        };
8332        let code = encoder.encode(&op).unwrap();
8333        // VMRS + BIC.W + ORR.W + VMSR + VCVT + VMRS + BIC.W + VMSR + VCVT.F32.S32
8334        assert_eq!(code.len(), 36);
8335    }
8336
8337    #[test]
8338    fn test_encode_f32_min_arm32() {
8339        let encoder = ArmEncoder::new_arm32();
8340        let op = ArmOp::F32Min {
8341            sd: VfpReg::S0,
8342            sn: VfpReg::S2,
8343            sm: VfpReg::S4,
8344        };
8345        let code = encoder.encode(&op).unwrap();
8346        assert_eq!(code.len(), 16); // VMOV + VCMP + VMRS + conditional VMOV
8347    }
8348
8349    #[test]
8350    fn test_encode_f32_max_thumb2() {
8351        let encoder = ArmEncoder::new_thumb2();
8352        let op = ArmOp::F32Max {
8353            sd: VfpReg::S0,
8354            sn: VfpReg::S2,
8355            sm: VfpReg::S4,
8356        };
8357        let code = encoder.encode(&op).unwrap();
8358        // VMOV(4) + VCMP(4) + VMRS(4) + IT(2) + VMOV(4) = 18
8359        assert_eq!(code.len(), 18);
8360    }
8361
8362    #[test]
8363    fn test_encode_f32_copysign_arm32() {
8364        let encoder = ArmEncoder::new_arm32();
8365        let op = ArmOp::F32Copysign {
8366            sd: VfpReg::S0,
8367            sn: VfpReg::S2,
8368            sm: VfpReg::S4,
8369        };
8370        let code = encoder.encode(&op).unwrap();
8371        // VMOV + VMOV + AND + BIC + ORR + VMOV = 6 * 4 = 24
8372        assert_eq!(code.len(), 24);
8373    }
8374
8375    // ========================================================================
8376    // f64 encoding tests
8377    // ========================================================================
8378
8379    #[test]
8380    fn test_encode_f64_add_arm32() {
8381        let encoder = ArmEncoder::new_arm32();
8382        let op = ArmOp::F64Add {
8383            dd: VfpReg::D0,
8384            dn: VfpReg::D1,
8385            dm: VfpReg::D2,
8386        };
8387        let code = encoder.encode(&op).unwrap();
8388        assert_eq!(code.len(), 4);
8389        // VADD.F64 D0, D1, D2: check coprocessor is cp11 (0xB)
8390        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8391        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11
8392    }
8393
8394    #[test]
8395    fn test_encode_f64_sub_thumb2() {
8396        let encoder = ArmEncoder::new_thumb2();
8397        let op = ArmOp::F64Sub {
8398            dd: VfpReg::D0,
8399            dn: VfpReg::D1,
8400            dm: VfpReg::D2,
8401        };
8402        let code = encoder.encode(&op).unwrap();
8403        assert_eq!(code.len(), 4); // 32-bit VFP as two Thumb halfwords
8404    }
8405
8406    #[test]
8407    fn test_encode_f64_mul_arm32() {
8408        let encoder = ArmEncoder::new_arm32();
8409        let op = ArmOp::F64Mul {
8410            dd: VfpReg::D0,
8411            dn: VfpReg::D1,
8412            dm: VfpReg::D2,
8413        };
8414        let code = encoder.encode(&op).unwrap();
8415        assert_eq!(code.len(), 4);
8416    }
8417
8418    #[test]
8419    fn test_encode_f64_div_arm32() {
8420        let encoder = ArmEncoder::new_arm32();
8421        let op = ArmOp::F64Div {
8422            dd: VfpReg::D0,
8423            dn: VfpReg::D1,
8424            dm: VfpReg::D2,
8425        };
8426        let code = encoder.encode(&op).unwrap();
8427        assert_eq!(code.len(), 4);
8428    }
8429
8430    #[test]
8431    fn test_encode_f64_abs_arm32() {
8432        let encoder = ArmEncoder::new_arm32();
8433        let op = ArmOp::F64Abs {
8434            dd: VfpReg::D0,
8435            dm: VfpReg::D2,
8436        };
8437        let code = encoder.encode(&op).unwrap();
8438        assert_eq!(code.len(), 4);
8439    }
8440
8441    #[test]
8442    fn test_encode_f64_neg_arm32() {
8443        let encoder = ArmEncoder::new_arm32();
8444        let op = ArmOp::F64Neg {
8445            dd: VfpReg::D0,
8446            dm: VfpReg::D2,
8447        };
8448        let code = encoder.encode(&op).unwrap();
8449        assert_eq!(code.len(), 4);
8450    }
8451
8452    #[test]
8453    fn test_encode_f64_sqrt_arm32() {
8454        let encoder = ArmEncoder::new_arm32();
8455        let op = ArmOp::F64Sqrt {
8456            dd: VfpReg::D0,
8457            dm: VfpReg::D2,
8458        };
8459        let code = encoder.encode(&op).unwrap();
8460        assert_eq!(code.len(), 4);
8461    }
8462
8463    #[test]
8464    fn test_encode_f64_load_arm32() {
8465        let encoder = ArmEncoder::new_arm32();
8466        let op = ArmOp::F64Load {
8467            dd: VfpReg::D0,
8468            addr: MemAddr::imm(Reg::R0, 8),
8469        };
8470        let code = encoder.encode(&op).unwrap();
8471        assert_eq!(code.len(), 4);
8472        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8473        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11 for F64
8474        assert_eq!(instr & 0xFF, 2); // offset 8 / 4 = 2
8475    }
8476
8477    #[test]
8478    fn test_encode_f64_store_thumb2() {
8479        let encoder = ArmEncoder::new_thumb2();
8480        let op = ArmOp::F64Store {
8481            dd: VfpReg::D0,
8482            addr: MemAddr::imm(Reg::SP, 0),
8483        };
8484        let code = encoder.encode(&op).unwrap();
8485        assert_eq!(code.len(), 4);
8486    }
8487
8488    #[test]
8489    fn test_encode_f64_compare_arm32() {
8490        let encoder = ArmEncoder::new_arm32();
8491        let op = ArmOp::F64Eq {
8492            rd: Reg::R0,
8493            dn: VfpReg::D0,
8494            dm: VfpReg::D1,
8495        };
8496        let code = encoder.encode(&op).unwrap();
8497        assert_eq!(code.len(), 16); // VCMP + VMRS + MOV #0 + MOVcond #1
8498    }
8499
8500    #[test]
8501    fn test_encode_f64_compare_thumb2() {
8502        let encoder = ArmEncoder::new_thumb2();
8503        let op = ArmOp::F64Lt {
8504            rd: Reg::R0,
8505            dn: VfpReg::D0,
8506            dm: VfpReg::D1,
8507        };
8508        let code = encoder.encode(&op).unwrap();
8509        // VCMP(4) + VMRS(4) + MOVS(2) + IT(2) + MOV(2) = 14
8510        assert_eq!(code.len(), 14);
8511    }
8512
8513    #[test]
8514    fn test_encode_f64_const_arm32() {
8515        let encoder = ArmEncoder::new_arm32();
8516        let op = ArmOp::F64Const {
8517            dd: VfpReg::D0,
8518            value: 3.125,
8519        };
8520        let code = encoder.encode(&op).unwrap();
8521        // MOVW(4) + MOVT(4) + MOVW(4) + MOVT(4) + VMOV(4) = 20
8522        assert_eq!(code.len(), 20);
8523    }
8524
8525    #[test]
8526    fn test_encode_f64_const_thumb2() {
8527        let encoder = ArmEncoder::new_thumb2();
8528        let op = ArmOp::F64Const {
8529            dd: VfpReg::D0,
8530            value: 2.5,
8531        };
8532        let code = encoder.encode(&op).unwrap();
8533        // MOVW(4) + MOVT(4) + MOVW(4) + MOVT(4) + VMOV(4) = 20
8534        assert_eq!(code.len(), 20);
8535    }
8536
8537    #[test]
8538    fn test_encode_f64_convert_i32s_arm32() {
8539        let encoder = ArmEncoder::new_arm32();
8540        let op = ArmOp::F64ConvertI32S {
8541            dd: VfpReg::D0,
8542            rm: Reg::R0,
8543        };
8544        let code = encoder.encode(&op).unwrap();
8545        // VMOV(4) + VCVT(4) = 8
8546        assert_eq!(code.len(), 8);
8547    }
8548
8549    #[test]
8550    fn test_encode_f64_promote_f32_arm32() {
8551        let encoder = ArmEncoder::new_arm32();
8552        let op = ArmOp::F64PromoteF32 {
8553            dd: VfpReg::D0,
8554            sm: VfpReg::S0,
8555        };
8556        let code = encoder.encode(&op).unwrap();
8557        assert_eq!(code.len(), 4); // Single VCVT.F64.F32 instruction
8558    }
8559
8560    #[test]
8561    fn test_encode_f64_promote_f32_thumb2() {
8562        let encoder = ArmEncoder::new_thumb2();
8563        let op = ArmOp::F64PromoteF32 {
8564            dd: VfpReg::D0,
8565            sm: VfpReg::S0,
8566        };
8567        let code = encoder.encode(&op).unwrap();
8568        assert_eq!(code.len(), 4);
8569    }
8570
8571    #[test]
8572    fn test_encode_i32_trunc_f64s_arm32() {
8573        let encoder = ArmEncoder::new_arm32();
8574        let op = ArmOp::I32TruncF64S {
8575            rd: Reg::R0,
8576            dm: VfpReg::D0,
8577        };
8578        let code = encoder.encode(&op).unwrap();
8579        // VCVT(4) + VMOV(4) = 8
8580        assert_eq!(code.len(), 8);
8581    }
8582
8583    #[test]
8584    fn test_encode_f64_reinterpret_i64_arm32() {
8585        let encoder = ArmEncoder::new_arm32();
8586        let op = ArmOp::F64ReinterpretI64 {
8587            dd: VfpReg::D0,
8588            rmlo: Reg::R0,
8589            rmhi: Reg::R1,
8590        };
8591        let code = encoder.encode(&op).unwrap();
8592        assert_eq!(code.len(), 4); // Single VMOV instruction
8593    }
8594
8595    #[test]
8596    fn test_encode_i64_reinterpret_f64_thumb2() {
8597        let encoder = ArmEncoder::new_thumb2();
8598        let op = ArmOp::I64ReinterpretF64 {
8599            rdlo: Reg::R0,
8600            rdhi: Reg::R1,
8601            dm: VfpReg::D0,
8602        };
8603        let code = encoder.encode(&op).unwrap();
8604        assert_eq!(code.len(), 4);
8605    }
8606
8607    #[test]
8608    fn test_encode_f64_trunc_thumb2() {
8609        let encoder = ArmEncoder::new_thumb2();
8610        let op = ArmOp::F64Trunc {
8611            dd: VfpReg::D0,
8612            dm: VfpReg::D1,
8613        };
8614        let code = encoder.encode(&op).unwrap();
8615        // Two VFP instructions via Thumb encoding
8616        assert_eq!(code.len(), 8);
8617    }
8618
8619    #[test]
8620    fn test_encode_f64_min_arm32() {
8621        let encoder = ArmEncoder::new_arm32();
8622        let op = ArmOp::F64Min {
8623            dd: VfpReg::D0,
8624            dn: VfpReg::D1,
8625            dm: VfpReg::D2,
8626        };
8627        let code = encoder.encode(&op).unwrap();
8628        // VMOV + VCMP + VMRS + conditional VMOV = 16
8629        assert_eq!(code.len(), 16);
8630    }
8631
8632    #[test]
8633    fn test_f64_cp11_encoding() {
8634        // Verify that F64 instructions use coprocessor 11 (0xB), not 10 (0xA)
8635        let encoder = ArmEncoder::new_arm32();
8636
8637        // F64Add
8638        let code = encoder
8639            .encode(&ArmOp::F64Add {
8640                dd: VfpReg::D0,
8641                dn: VfpReg::D0,
8642                dm: VfpReg::D0,
8643            })
8644            .unwrap();
8645        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8646        assert_eq!((instr >> 8) & 0xF, 0xB, "F64 should use cp11");
8647
8648        // F32Add for comparison
8649        let code = encoder
8650            .encode(&ArmOp::F32Add {
8651                sd: VfpReg::S0,
8652                sn: VfpReg::S0,
8653                sm: VfpReg::S0,
8654            })
8655            .unwrap();
8656        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8657        assert_eq!((instr >> 8) & 0xF, 0xA, "F32 should use cp10");
8658    }
8659
8660    #[test]
8661    fn test_dreg_encoding_higher_registers() {
8662        let encoder = ArmEncoder::new_arm32();
8663
8664        // Test with D15 (highest register)
8665        let op = ArmOp::F64Add {
8666            dd: VfpReg::D15,
8667            dn: VfpReg::D14,
8668            dm: VfpReg::D13,
8669        };
8670        let code = encoder.encode(&op).unwrap();
8671        assert_eq!(code.len(), 4);
8672
8673        // Verify the register encoding worked (instruction is valid)
8674        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8675        assert_eq!((instr >> 8) & 0xF, 0xB); // cp11
8676    }
8677
8678    // ========================================================================
8679    // Control flow encoding tests
8680    // ========================================================================
8681
8682    #[test]
8683    fn test_encode_label_emits_no_bytes() {
8684        let encoder = ArmEncoder::new_thumb2();
8685        let op = ArmOp::Label {
8686            name: ".Lblock_end_0".to_string(),
8687        };
8688        let code = encoder.encode(&op).unwrap();
8689        assert!(code.is_empty(), "Label should emit zero bytes");
8690
8691        let encoder32 = ArmEncoder::new_arm32();
8692        let code32 = encoder32.encode(&op).unwrap();
8693        assert!(
8694            code32.is_empty(),
8695            "Label should emit zero bytes in ARM32 too"
8696        );
8697    }
8698
8699    #[test]
8700    fn test_encode_bcc_eq_thumb2() {
8701        use synth_synthesis::Condition;
8702        let encoder = ArmEncoder::new_thumb2();
8703        let op = ArmOp::Bcc {
8704            cond: Condition::EQ,
8705            label: "target".to_string(),
8706        };
8707        let code = encoder.encode(&op).unwrap();
8708        assert_eq!(code.len(), 2); // 16-bit conditional branch
8709
8710        // BEQ with offset 0: 0xD000 in little-endian
8711        assert_eq!(code, vec![0x00, 0xD0]);
8712    }
8713
8714    #[test]
8715    fn test_encode_bcc_ne_thumb2() {
8716        use synth_synthesis::Condition;
8717        let encoder = ArmEncoder::new_thumb2();
8718        let op = ArmOp::Bcc {
8719            cond: Condition::NE,
8720            label: "target".to_string(),
8721        };
8722        let code = encoder.encode(&op).unwrap();
8723        assert_eq!(code.len(), 2);
8724
8725        // BNE with offset 0: 0xD100 in little-endian
8726        assert_eq!(code, vec![0x00, 0xD1]);
8727    }
8728
8729    #[test]
8730    fn test_encode_bcc_arm32() {
8731        use synth_synthesis::Condition;
8732        let encoder = ArmEncoder::new_arm32();
8733        let op = ArmOp::Bcc {
8734            cond: Condition::EQ,
8735            label: "target".to_string(),
8736        };
8737        let code = encoder.encode(&op).unwrap();
8738        assert_eq!(code.len(), 4); // 32-bit ARM instruction
8739
8740        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
8741        // BEQ: cond=0x0, opcode=0xA, offset=0
8742        assert_eq!(instr & 0xF0000000, 0x00000000); // EQ condition
8743        assert_eq!(instr & 0x0F000000, 0x0A000000); // Branch opcode
8744    }
8745
8746    #[test]
8747    fn test_encode_udf_thumb2() {
8748        let encoder = ArmEncoder::new_thumb2();
8749        let op = ArmOp::Udf { imm: 0 };
8750        let code = encoder.encode(&op).unwrap();
8751        assert_eq!(code.len(), 2); // 16-bit
8752
8753        // UDF #0: 0xDE00 in little-endian
8754        assert_eq!(code, vec![0x00, 0xDE]);
8755    }
8756
8757    /// #610: the i64 rot/div/rem expansions must land the result in the
8758    /// selector-assigned rd pair and leave R0-R3 preserved (restored from the
8759    /// fixed-ABI wrapper's save area) — pre-#610 the rot expansion's own
8760    /// `POP {R4}` restored stale scratch OVER the result (rd_lo == R4) and
8761    /// the div/rem expansions ignored their register fields outright.
8762    #[test]
8763    fn test_610_i64_rot_expansion_ends_with_rd_movs_and_restore() {
8764        let encoder = ArmEncoder::new_thumb2();
8765        for op in [
8766            ArmOp::I64Rotl {
8767                rdlo: Reg::R4,
8768                rdhi: Reg::R5,
8769                rnlo: Reg::R0,
8770                rnhi: Reg::R1,
8771                shift: Reg::R2,
8772            },
8773            ArmOp::I64Rotr {
8774                rdlo: Reg::R4,
8775                rdhi: Reg::R5,
8776                rnlo: Reg::R0,
8777                rnhi: Reg::R1,
8778                shift: Reg::R2,
8779            },
8780        ] {
8781            let code = encoder.encode(&op).unwrap();
8782            assert_eq!(code.len(), 102, "register-independent size (estimator pin)");
8783            // Tail: MOV r5, r1 (0x460D); MOV r4, r0 (0x4604); POP {r0..r3}
8784            // (rd pair r4:r5 does not overlap the save area — all 4 restored).
8785            let tail: Vec<u16> = code[code.len() - 12..]
8786                .chunks(2)
8787                .map(|c| u16::from_le_bytes([c[0], c[1]]))
8788                .collect();
8789            assert_eq!(tail, vec![0x460D, 0x4604, 0xBC01, 0xBC02, 0xBC04, 0xBC08]);
8790        }
8791    }
8792
8793    /// #610: div/rem expansions honor rd and carry the divide-by-zero trap
8794    /// guard (`ORRS R12, R2, R3; BNE +0; UDF #0`) after operand marshaling.
8795    #[test]
8796    fn test_610_i64_div_rem_expansion_guard_and_rd() {
8797        let encoder = ArmEncoder::new_thumb2();
8798        let mk = |which: u8| {
8799            let (rdlo, rdhi, rnlo, rnhi, rmlo, rmhi) =
8800                (Reg::R4, Reg::R5, Reg::R0, Reg::R1, Reg::R2, Reg::R3);
8801            match which {
8802                0 => ArmOp::I64DivU {
8803                    rdlo,
8804                    rdhi,
8805                    rnlo,
8806                    rnhi,
8807                    rmlo,
8808                    rmhi,
8809                },
8810                1 => ArmOp::I64RemU {
8811                    rdlo,
8812                    rdhi,
8813                    rnlo,
8814                    rnhi,
8815                    rmlo,
8816                    rmhi,
8817                },
8818                2 => ArmOp::I64DivS {
8819                    rdlo,
8820                    rdhi,
8821                    rnlo,
8822                    rnhi,
8823                    rmlo,
8824                    rmhi,
8825                },
8826                _ => ArmOp::I64RemS {
8827                    rdlo,
8828                    rdhi,
8829                    rnlo,
8830                    rnhi,
8831                    rmlo,
8832                    rmhi,
8833                },
8834            }
8835        };
8836        for which in 0..4u8 {
8837            let code = encoder.encode(&mk(which)).unwrap();
8838            // Zero-divisor trap guard right after the 26-byte marshal prologue.
8839            let guard: Vec<u16> = code[26..34]
8840                .chunks(2)
8841                .map(|c| u16::from_le_bytes([c[0], c[1]]))
8842                .collect();
8843            assert_eq!(
8844                guard,
8845                vec![0xEA52, 0x0C03, 0xD100, 0xDE00],
8846                "ORRS R12,R2,R3; BNE +0; UDF #0"
8847            );
8848            // Tail: result into rd pair (r5:r4), then restore all of R0-R3.
8849            let tail: Vec<u16> = code[code.len() - 12..]
8850                .chunks(2)
8851                .map(|c| u16::from_le_bytes([c[0], c[1]]))
8852                .collect();
8853            assert_eq!(tail, vec![0x460D, 0x4604, 0xBC01, 0xBC02, 0xBC04, 0xBC08]);
8854        }
8855    }
8856
8857    /// #610: when rd overlaps R0-R3 the restore must SKIP the result
8858    /// registers (drop the saved caller word) instead of popping over them.
8859    #[test]
8860    fn test_610_i64_divu_rd_in_r0_r1_skips_restore() {
8861        let encoder = ArmEncoder::new_thumb2();
8862        let code = encoder
8863            .encode(&ArmOp::I64DivU {
8864                rdlo: Reg::R0,
8865                rdhi: Reg::R1,
8866                rnlo: Reg::R0,
8867                rnhi: Reg::R1,
8868                rmlo: Reg::R2,
8869                rmhi: Reg::R3,
8870            })
8871            .unwrap();
8872        let tail: Vec<u16> = code[code.len() - 12..]
8873            .chunks(2)
8874            .map(|c| u16::from_le_bytes([c[0], c[1]]))
8875            .collect();
8876        // MOV r1,r1 / MOV r0,r0 (no-ops, size-stable), ADD SP,#4 twice
8877        // (discard saved r0/r1 — the result lives there), POP {r2}, POP {r3}.
8878        assert_eq!(tail, vec![0x4609, 0x4600, 0xB001, 0xB001, 0xBC04, 0xBC08]);
8879    }
8880
8881    /// #610: a fully swapped rd pair (rd_lo=R1, rd_hi=R0) cannot be
8882    /// materialized by two MOVs in either order — must be a loud Err, never
8883    /// silent corruption. (Selector pairs are consecutive, so unreachable.)
8884    #[test]
8885    fn test_610_i64_swapped_rd_pair_rejected() {
8886        let encoder = ArmEncoder::new_thumb2();
8887        let result = encoder.encode(&ArmOp::I64RemU {
8888            rdlo: Reg::R1,
8889            rdhi: Reg::R0,
8890            rnlo: Reg::R2,
8891            rnhi: Reg::R3,
8892            rmlo: Reg::R4,
8893            rmhi: Reg::R5,
8894        });
8895        assert!(result.is_err(), "swapped rd pair must be rejected loudly");
8896    }
8897
8898    #[test]
8899    fn test_encode_nop_thumb2() {
8900        let encoder = ArmEncoder::new_thumb2();
8901        let op = ArmOp::Nop;
8902        let code = encoder.encode(&op).unwrap();
8903        assert_eq!(code.len(), 2); // 16-bit
8904
8905        // NOP: 0xBF00 in little-endian
8906        assert_eq!(code, vec![0x00, 0xBF]);
8907    }
8908
8909    // =========================================================================
8910    // i64 Thumb-2 encoding tests
8911    // =========================================================================
8912
8913    #[test]
8914    fn test_encode_i64_add_thumb2() {
8915        let encoder = ArmEncoder::new_thumb2();
8916        let op = ArmOp::I64Add {
8917            rdlo: Reg::R0,
8918            rdhi: Reg::R1,
8919            rnlo: Reg::R0,
8920            rnhi: Reg::R1,
8921            rmlo: Reg::R2,
8922            rmhi: Reg::R3,
8923        };
8924        let code = encoder.encode(&op).unwrap();
8925        // Should emit ADDS (2 bytes) + ADC.W (4 bytes) = 6 bytes
8926        assert_eq!(code.len(), 6, "I64Add should be 6 bytes (ADDS + ADC.W)");
8927    }
8928
8929    #[test]
8930    fn test_encode_i64_sub_thumb2() {
8931        let encoder = ArmEncoder::new_thumb2();
8932        let op = ArmOp::I64Sub {
8933            rdlo: Reg::R0,
8934            rdhi: Reg::R1,
8935            rnlo: Reg::R0,
8936            rnhi: Reg::R1,
8937            rmlo: Reg::R2,
8938            rmhi: Reg::R3,
8939        };
8940        let code = encoder.encode(&op).unwrap();
8941        // Should emit SUBS (2 bytes) + SBC.W (4 bytes) = 6 bytes
8942        assert_eq!(code.len(), 6, "I64Sub should be 6 bytes (SUBS + SBC.W)");
8943    }
8944
8945    #[test]
8946    fn test_encode_i64_and_thumb2() {
8947        let encoder = ArmEncoder::new_thumb2();
8948        let op = ArmOp::I64And {
8949            rdlo: Reg::R0,
8950            rdhi: Reg::R1,
8951            rnlo: Reg::R0,
8952            rnhi: Reg::R1,
8953            rmlo: Reg::R2,
8954            rmhi: Reg::R3,
8955        };
8956        let code = encoder.encode(&op).unwrap();
8957        // AND.W (4 bytes) + AND.W (4 bytes) = 8 bytes
8958        assert!(code.len() >= 4, "I64And should emit at least 4 bytes");
8959    }
8960
8961    #[test]
8962    fn test_encode_i64_or_thumb2() {
8963        let encoder = ArmEncoder::new_thumb2();
8964        let op = ArmOp::I64Or {
8965            rdlo: Reg::R0,
8966            rdhi: Reg::R1,
8967            rnlo: Reg::R0,
8968            rnhi: Reg::R1,
8969            rmlo: Reg::R2,
8970            rmhi: Reg::R3,
8971        };
8972        let code = encoder.encode(&op).unwrap();
8973        assert!(code.len() >= 4, "I64Or should emit at least 4 bytes");
8974    }
8975
8976    #[test]
8977    fn test_encode_i64_xor_thumb2() {
8978        let encoder = ArmEncoder::new_thumb2();
8979        let op = ArmOp::I64Xor {
8980            rdlo: Reg::R0,
8981            rdhi: Reg::R1,
8982            rnlo: Reg::R0,
8983            rnhi: Reg::R1,
8984            rmlo: Reg::R2,
8985            rmhi: Reg::R3,
8986        };
8987        let code = encoder.encode(&op).unwrap();
8988        assert!(code.len() >= 4, "I64Xor should emit at least 4 bytes");
8989    }
8990
8991    #[test]
8992    fn test_encode_i64_const_small_thumb2() {
8993        let encoder = ArmEncoder::new_thumb2();
8994        // Small constant: only needs MOVW for each half
8995        let op = ArmOp::I64Const {
8996            rdlo: Reg::R0,
8997            rdhi: Reg::R1,
8998            value: 42,
8999        };
9000        let code = encoder.encode(&op).unwrap();
9001        // MOVW R0, #42 (4 bytes) + MOVW R1, #0 (4 bytes) = 8 bytes minimum
9002        assert!(code.len() >= 8, "I64Const should emit at least 8 bytes");
9003    }
9004
9005    #[test]
9006    fn test_encode_i64_const_large_thumb2() {
9007        let encoder = ArmEncoder::new_thumb2();
9008        // Large constant: needs MOVW+MOVT for each half
9009        let op = ArmOp::I64Const {
9010            rdlo: Reg::R0,
9011            rdhi: Reg::R1,
9012            value: 0x1234_5678_9ABC_DEF0_u64 as i64,
9013        };
9014        let code = encoder.encode(&op).unwrap();
9015        // MOVW + MOVT for lo (8 bytes) + MOVW + MOVT for hi (8 bytes) = 16 bytes
9016        assert_eq!(
9017            code.len(),
9018            16,
9019            "I64Const with large value should be 16 bytes"
9020        );
9021    }
9022
9023    #[test]
9024    fn test_encode_i64_extend_i32_s_thumb2() {
9025        let encoder = ArmEncoder::new_thumb2();
9026        let op = ArmOp::I64ExtendI32S {
9027            rdlo: Reg::R0,
9028            rdhi: Reg::R1,
9029            rn: Reg::R0,
9030        };
9031        let code = encoder.encode(&op).unwrap();
9032        // When rdlo == rn, only ASR (4 bytes) is emitted
9033        assert_eq!(
9034            code.len(),
9035            4,
9036            "I64ExtendI32S (same reg) should be 4 bytes (ASR only)"
9037        );
9038    }
9039
9040    #[test]
9041    fn test_encode_i64_extend_i32_s_diff_reg_thumb2() {
9042        let encoder = ArmEncoder::new_thumb2();
9043        let op = ArmOp::I64ExtendI32S {
9044            rdlo: Reg::R0,
9045            rdhi: Reg::R1,
9046            rn: Reg::R2,
9047        };
9048        let code = encoder.encode(&op).unwrap();
9049        // MOV rdlo, rn (2 bytes for low regs) + ASR rdhi, rdlo, #31 (4 bytes) = 6 bytes
9050        assert!(
9051            code.len() >= 6,
9052            "I64ExtendI32S (diff reg) should be at least 6 bytes"
9053        );
9054    }
9055
9056    #[test]
9057    fn test_encode_i64_extend_i32_u_thumb2() {
9058        let encoder = ArmEncoder::new_thumb2();
9059        let op = ArmOp::I64ExtendI32U {
9060            rdlo: Reg::R0,
9061            rdhi: Reg::R1,
9062            rn: Reg::R0,
9063        };
9064        let code = encoder.encode(&op).unwrap();
9065        // When rdlo == rn, only MOV rdhi, #0 (2 bytes) is emitted
9066        assert_eq!(
9067            code.len(),
9068            2,
9069            "I64ExtendI32U (same reg) should be 2 bytes (MOV #0 only)"
9070        );
9071    }
9072
9073    #[test]
9074    fn test_encode_i32_wrap_i64_nop_thumb2() {
9075        let encoder = ArmEncoder::new_thumb2();
9076        // When rd == rnlo, should be a NOP
9077        let op = ArmOp::I32WrapI64 {
9078            rd: Reg::R0,
9079            rnlo: Reg::R0,
9080        };
9081        let code = encoder.encode(&op).unwrap();
9082        assert_eq!(code.len(), 2, "I32WrapI64 same reg should be NOP (2 bytes)");
9083        assert_eq!(code, vec![0x00, 0xBF]); // NOP
9084    }
9085
9086    #[test]
9087    fn test_encode_i32_wrap_i64_diff_reg_thumb2() {
9088        let encoder = ArmEncoder::new_thumb2();
9089        let op = ArmOp::I32WrapI64 {
9090            rd: Reg::R2,
9091            rnlo: Reg::R0,
9092        };
9093        let code = encoder.encode(&op).unwrap();
9094        // MOV R2, R0 (2 or 4 bytes)
9095        assert!(
9096            code.len() >= 2,
9097            "I32WrapI64 diff reg should emit at least 2 bytes"
9098        );
9099    }
9100
9101    #[test]
9102    fn test_encode_i64_eqz_thumb2() {
9103        let encoder = ArmEncoder::new_thumb2();
9104        let op = ArmOp::I64Eqz {
9105            rd: Reg::R0,
9106            rnlo: Reg::R0,
9107            rnhi: Reg::R1,
9108        };
9109        let code = encoder.encode(&op).unwrap();
9110        // Delegates to I64SetCondZ which is already encoded
9111        assert!(
9112            code.len() >= 6,
9113            "I64Eqz should emit at least 6 bytes for ORR+ITE+MOV+MOV"
9114        );
9115    }
9116
9117    #[test]
9118    fn test_encode_i64_eq_thumb2() {
9119        let encoder = ArmEncoder::new_thumb2();
9120        let op = ArmOp::I64Eq {
9121            rd: Reg::R0,
9122            rnlo: Reg::R0,
9123            rnhi: Reg::R1,
9124            rmlo: Reg::R2,
9125            rmhi: Reg::R3,
9126        };
9127        let code = encoder.encode(&op).unwrap();
9128        // Delegates to I64SetCond EQ: CMP lo + IT EQ + CMPEQ hi + ITE EQ + MOV 1 + MOV 0
9129        assert!(code.len() >= 10, "I64Eq should emit at least 10 bytes");
9130    }
9131
9132    #[test]
9133    fn test_encode_i64_ldr_thumb2() {
9134        let encoder = ArmEncoder::new_thumb2();
9135        let op = ArmOp::I64Ldr {
9136            rdlo: Reg::R0,
9137            rdhi: Reg::R1,
9138            addr: MemAddr::imm(Reg::SP, 0),
9139        };
9140        let code = encoder.encode(&op).unwrap();
9141        // Two LDR instructions (lo at offset, hi at offset+4)
9142        assert!(code.len() >= 4, "I64Ldr should emit at least 4 bytes");
9143    }
9144
9145    #[test]
9146    fn test_372_i64_ldr_indexed_materializes_address() {
9147        // #372: a memory i64.load carries an index register (R11 + addr + off).
9148        // The encoder must materialize `ip = base + index` (ADD.W) and load via
9149        // `[ip,#off]` — NOT drop the index. A frame (non-indexed) i64.load must
9150        // stay byte-identical (plain `[base,#off]`, no ADD).
9151        let encoder = ArmEncoder::new_thumb2();
9152        let indexed = encoder
9153            .encode(&ArmOp::I64Ldr {
9154                rdlo: Reg::R0,
9155                rdhi: Reg::R1,
9156                addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 0),
9157            })
9158            .unwrap();
9159        // ADD.W ip, fp, r0 = eb0b 0c00 (byte-verified vs arm-none-eabi-as).
9160        assert_eq!(
9161            &indexed[0..4],
9162            &[0x0b, 0xeb, 0x00, 0x0c],
9163            "indexed I64Ldr must start with ADD.W ip, base, index"
9164        );
9165        let frame = encoder
9166            .encode(&ArmOp::I64Ldr {
9167                rdlo: Reg::R0,
9168                rdhi: Reg::R1,
9169                addr: MemAddr::imm(Reg::SP, 8),
9170            })
9171            .unwrap();
9172        // No index -> no ADD.W prefix (byte-identical frame access).
9173        assert_ne!(
9174            &frame[0..2],
9175            &[0x0b, 0xeb],
9176            "frame (non-indexed) I64Ldr must NOT emit an ADD.W"
9177        );
9178    }
9179
9180    #[test]
9181    fn test_382_i64_ldst_large_offset_materializes_not_skips() {
9182        // #382: an indexed i64.load/store whose static offset > 0xFFF must
9183        // MATERIALIZE the offset into the base — NOT return Err (skip the fn).
9184        // Sequence for reg_imm(R11, R0, 5000): MOVW ip,#5000 ; ADD ip,r0,ip ;
9185        // ADD ip,ip,fp ; LDR/STR halves at [ip,#0] / [ip,#4]. Byte-verified tail
9186        // vs arm-none-eabi-as.
9187        let encoder = ArmEncoder::new_thumb2();
9188        // 0x1388 > 0xFFF (MemAddr is not Copy, so build it per use).
9189
9190        let ld = encoder
9191            .encode(&ArmOp::I64Ldr {
9192                rdlo: Reg::R0,
9193                rdhi: Reg::R1,
9194                addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 5000),
9195            })
9196            .expect("large-offset i64.load must lower, not skip");
9197        // MOVW ip,#0x1388 (4) + ADD ip,r0,ip (4) + ADD ip,ip,fp (4) + 2 LDR (8).
9198        assert_eq!(ld.len(), 20, "expected MOVW + 2×ADD + 2×LDR");
9199        // Must NOT be the small-offset `ADD.W ip, fp, r0` (0x0b 0xeb) prefix —
9200        // that path can only reach imm12 offsets.
9201        assert_ne!(
9202            &ld[0..2],
9203            &[0x0b, 0xeb],
9204            "must materialize the large offset"
9205        );
9206        // Effective base built in ip, then halves at [ip,#0] / [ip,#4].
9207        assert_eq!(
9208            &ld[4..20],
9209            &[
9210                0x00, 0xeb, 0x0c, 0x0c, // ADD.W ip, r0, ip
9211                0x0c, 0xeb, 0x0b, 0x0c, // ADD.W ip, ip, fp
9212                0xdc, 0xf8, 0x00, 0x00, // LDR.W r0, [ip, #0]
9213                0xdc, 0xf8, 0x04, 0x10, // LDR.W r1, [ip, #4]
9214            ],
9215            "large-offset i64.load must fold offset into ip and access [ip,#0]/[ip,#4]"
9216        );
9217
9218        // Store: same base materialization, STR halves.
9219        let st = encoder
9220            .encode(&ArmOp::I64Str {
9221                rdlo: Reg::R2,
9222                rdhi: Reg::R3,
9223                addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 5000),
9224            })
9225            .expect("large-offset i64.store must lower, not skip");
9226        assert_eq!(st.len(), 20);
9227        assert_eq!(
9228            &st[4..20],
9229            &[
9230                0x00, 0xeb, 0x0c, 0x0c, // ADD.W ip, r0, ip
9231                0x0c, 0xeb, 0x0b, 0x0c, // ADD.W ip, ip, fp
9232                0xcc, 0xf8, 0x00, 0x20, // STR.W r2, [ip, #0]
9233                0xcc, 0xf8, 0x04, 0x30, // STR.W r3, [ip, #4]
9234            ],
9235            "large-offset i64.store must fold offset into ip and access [ip,#0]/[ip,#4]"
9236        );
9237
9238        // Small-offset (imm12) indexed access stays byte-identical (#372): the
9239        // effective base is a single `ADD.W ip, fp, r0` and the halves keep the
9240        // folded immediates — NO extra MOVW/ADD.
9241        let small = encoder
9242            .encode(&ArmOp::I64Ldr {
9243                rdlo: Reg::R0,
9244                rdhi: Reg::R1,
9245                addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 8),
9246            })
9247            .unwrap();
9248        assert_eq!(
9249            &small[0..4],
9250            &[0x0b, 0xeb, 0x00, 0x0c],
9251            "small-offset indexed i64 must keep the single ADD.W ip, fp, r0"
9252        );
9253        assert_eq!(small.len(), 12, "ADD.W + 2×LDR.W (offset folded in imm12)");
9254    }
9255
9256    #[test]
9257    fn test_encode_i64_str_thumb2() {
9258        let encoder = ArmEncoder::new_thumb2();
9259        let op = ArmOp::I64Str {
9260            rdlo: Reg::R0,
9261            rdhi: Reg::R1,
9262            addr: MemAddr::imm(Reg::SP, 0),
9263        };
9264        let code = encoder.encode(&op).unwrap();
9265        // Two STR instructions (lo at offset, hi at offset+4)
9266        assert!(code.len() >= 4, "I64Str should emit at least 4 bytes");
9267    }
9268
9269    #[test]
9270    fn test_encode_i64_all_comparisons_thumb2() {
9271        let encoder = ArmEncoder::new_thumb2();
9272
9273        let ops = vec![
9274            ArmOp::I64Ne {
9275                rd: Reg::R0,
9276                rnlo: Reg::R0,
9277                rnhi: Reg::R1,
9278                rmlo: Reg::R2,
9279                rmhi: Reg::R3,
9280            },
9281            ArmOp::I64LtS {
9282                rd: Reg::R0,
9283                rnlo: Reg::R0,
9284                rnhi: Reg::R1,
9285                rmlo: Reg::R2,
9286                rmhi: Reg::R3,
9287            },
9288            ArmOp::I64LtU {
9289                rd: Reg::R0,
9290                rnlo: Reg::R0,
9291                rnhi: Reg::R1,
9292                rmlo: Reg::R2,
9293                rmhi: Reg::R3,
9294            },
9295            ArmOp::I64LeS {
9296                rd: Reg::R0,
9297                rnlo: Reg::R0,
9298                rnhi: Reg::R1,
9299                rmlo: Reg::R2,
9300                rmhi: Reg::R3,
9301            },
9302            ArmOp::I64LeU {
9303                rd: Reg::R0,
9304                rnlo: Reg::R0,
9305                rnhi: Reg::R1,
9306                rmlo: Reg::R2,
9307                rmhi: Reg::R3,
9308            },
9309            ArmOp::I64GtS {
9310                rd: Reg::R0,
9311                rnlo: Reg::R0,
9312                rnhi: Reg::R1,
9313                rmlo: Reg::R2,
9314                rmhi: Reg::R3,
9315            },
9316            ArmOp::I64GtU {
9317                rd: Reg::R0,
9318                rnlo: Reg::R0,
9319                rnhi: Reg::R1,
9320                rmlo: Reg::R2,
9321                rmhi: Reg::R3,
9322            },
9323            ArmOp::I64GeS {
9324                rd: Reg::R0,
9325                rnlo: Reg::R0,
9326                rnhi: Reg::R1,
9327                rmlo: Reg::R2,
9328                rmhi: Reg::R3,
9329            },
9330            ArmOp::I64GeU {
9331                rd: Reg::R0,
9332                rnlo: Reg::R0,
9333                rnhi: Reg::R1,
9334                rmlo: Reg::R2,
9335                rmhi: Reg::R3,
9336            },
9337        ];
9338
9339        for op in &ops {
9340            let code = encoder.encode(op).unwrap();
9341            assert!(
9342                code.len() >= 8,
9343                "i64 comparison {:?} should emit at least 8 bytes, got {}",
9344                op,
9345                code.len()
9346            );
9347        }
9348    }
9349
9350    #[test]
9351    fn test_encode_i64_const_zero_thumb2() {
9352        let encoder = ArmEncoder::new_thumb2();
9353        let op = ArmOp::I64Const {
9354            rdlo: Reg::R0,
9355            rdhi: Reg::R1,
9356            value: 0,
9357        };
9358        let code = encoder.encode(&op).unwrap();
9359        // MOVW R0, #0 (4 bytes) + MOVW R1, #0 (4 bytes) = 8 bytes
9360        assert_eq!(code.len(), 8, "I64Const(0) should be 8 bytes");
9361    }
9362
9363    #[test]
9364    fn test_encode_i64_const_negative_one_thumb2() {
9365        let encoder = ArmEncoder::new_thumb2();
9366        let op = ArmOp::I64Const {
9367            rdlo: Reg::R0,
9368            rdhi: Reg::R1,
9369            value: -1, // 0xFFFF_FFFF_FFFF_FFFF
9370        };
9371        let code = encoder.encode(&op).unwrap();
9372        // MOVW + MOVT for lo (8 bytes) + MOVW + MOVT for hi (8 bytes) = 16 bytes
9373        assert_eq!(code.len(), 16, "I64Const(-1) should be 16 bytes");
9374    }
9375
9376    // =========================================================================
9377    // Sub-word load/store encoding tests
9378    // =========================================================================
9379
9380    #[test]
9381    fn test_encode_ldrb_arm32() {
9382        let encoder = ArmEncoder::new_arm32();
9383        let op = ArmOp::Ldrb {
9384            rd: Reg::R0,
9385            addr: MemAddr::imm(Reg::R1, 4),
9386        };
9387        let code = encoder.encode(&op).unwrap();
9388        assert_eq!(code.len(), 4, "ARM32 LDRB should be 4 bytes");
9389        // LDRB R0, [R1, #4] = 0xE5D10004
9390        let encoded = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
9391        assert_eq!(encoded, 0xE5D10004, "Should encode LDRB R0, [R1, #4]");
9392    }
9393
9394    #[test]
9395    fn test_encode_strb_arm32() {
9396        let encoder = ArmEncoder::new_arm32();
9397        let op = ArmOp::Strb {
9398            rd: Reg::R0,
9399            addr: MemAddr::imm(Reg::R1, 0),
9400        };
9401        let code = encoder.encode(&op).unwrap();
9402        assert_eq!(code.len(), 4, "ARM32 STRB should be 4 bytes");
9403        // STRB R0, [R1, #0] = 0xE5C10000
9404        let encoded = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
9405        assert_eq!(encoded, 0xE5C10000, "Should encode STRB R0, [R1, #0]");
9406    }
9407
9408    #[test]
9409    fn test_encode_ldrh_arm32() {
9410        let encoder = ArmEncoder::new_arm32();
9411        let op = ArmOp::Ldrh {
9412            rd: Reg::R0,
9413            addr: MemAddr::imm(Reg::R1, 2),
9414        };
9415        let code = encoder.encode(&op).unwrap();
9416        assert_eq!(code.len(), 4, "ARM32 LDRH should be 4 bytes");
9417    }
9418
9419    #[test]
9420    fn test_encode_strh_arm32() {
9421        let encoder = ArmEncoder::new_arm32();
9422        let op = ArmOp::Strh {
9423            rd: Reg::R0,
9424            addr: MemAddr::imm(Reg::R1, 0),
9425        };
9426        let code = encoder.encode(&op).unwrap();
9427        assert_eq!(code.len(), 4, "ARM32 STRH should be 4 bytes");
9428    }
9429
9430    #[test]
9431    fn test_encode_ldrsb_arm32() {
9432        let encoder = ArmEncoder::new_arm32();
9433        let op = ArmOp::Ldrsb {
9434            rd: Reg::R0,
9435            addr: MemAddr::imm(Reg::R1, 0),
9436        };
9437        let code = encoder.encode(&op).unwrap();
9438        assert_eq!(code.len(), 4, "ARM32 LDRSB should be 4 bytes");
9439    }
9440
9441    #[test]
9442    fn test_encode_ldrsh_arm32() {
9443        let encoder = ArmEncoder::new_arm32();
9444        let op = ArmOp::Ldrsh {
9445            rd: Reg::R0,
9446            addr: MemAddr::imm(Reg::R1, 0),
9447        };
9448        let code = encoder.encode(&op).unwrap();
9449        assert_eq!(code.len(), 4, "ARM32 LDRSH should be 4 bytes");
9450    }
9451
9452    #[test]
9453    fn test_encode_ldrb_thumb2_16bit() {
9454        let encoder = ArmEncoder::new_thumb2();
9455        let op = ArmOp::Ldrb {
9456            rd: Reg::R0,
9457            addr: MemAddr::imm(Reg::R1, 4),
9458        };
9459        let code = encoder.encode(&op).unwrap();
9460        // Low registers + small offset -> 16-bit encoding
9461        assert_eq!(
9462            code.len(),
9463            2,
9464            "Thumb-2 LDRB with small offset should be 16-bit"
9465        );
9466    }
9467
9468    #[test]
9469    fn test_encode_ldrb_thumb2_32bit() {
9470        let encoder = ArmEncoder::new_thumb2();
9471        let op = ArmOp::Ldrb {
9472            rd: Reg::R0,
9473            addr: MemAddr::imm(Reg::R1, 100), // offset > 31 needs 32-bit
9474        };
9475        let code = encoder.encode(&op).unwrap();
9476        assert_eq!(
9477            code.len(),
9478            4,
9479            "Thumb-2 LDRB with large offset should be 32-bit"
9480        );
9481    }
9482
9483    #[test]
9484    fn test_encode_strb_thumb2_16bit() {
9485        let encoder = ArmEncoder::new_thumb2();
9486        let op = ArmOp::Strb {
9487            rd: Reg::R0,
9488            addr: MemAddr::imm(Reg::R1, 10),
9489        };
9490        let code = encoder.encode(&op).unwrap();
9491        assert_eq!(
9492            code.len(),
9493            2,
9494            "Thumb-2 STRB with small offset should be 16-bit"
9495        );
9496    }
9497
9498    #[test]
9499    fn test_encode_ldrh_thumb2_16bit() {
9500        let encoder = ArmEncoder::new_thumb2();
9501        let op = ArmOp::Ldrh {
9502            rd: Reg::R0,
9503            addr: MemAddr::imm(Reg::R1, 4), // offset aligned to 2, <= 62
9504        };
9505        let code = encoder.encode(&op).unwrap();
9506        assert_eq!(
9507            code.len(),
9508            2,
9509            "Thumb-2 LDRH with small aligned offset should be 16-bit"
9510        );
9511    }
9512
9513    #[test]
9514    fn test_encode_strh_thumb2_16bit() {
9515        let encoder = ArmEncoder::new_thumb2();
9516        let op = ArmOp::Strh {
9517            rd: Reg::R0,
9518            addr: MemAddr::imm(Reg::R1, 4),
9519        };
9520        let code = encoder.encode(&op).unwrap();
9521        assert_eq!(
9522            code.len(),
9523            2,
9524            "Thumb-2 STRH with small aligned offset should be 16-bit"
9525        );
9526    }
9527
9528    #[test]
9529    fn test_encode_ldrsb_thumb2() {
9530        let encoder = ArmEncoder::new_thumb2();
9531        let op = ArmOp::Ldrsb {
9532            rd: Reg::R0,
9533            addr: MemAddr::imm(Reg::R1, 0),
9534        };
9535        let code = encoder.encode(&op).unwrap();
9536        // LDRSB has no 16-bit immediate form, always 32-bit
9537        assert_eq!(code.len(), 4, "Thumb-2 LDRSB should be 32-bit");
9538    }
9539
9540    #[test]
9541    fn test_encode_ldrsh_thumb2() {
9542        let encoder = ArmEncoder::new_thumb2();
9543        let op = ArmOp::Ldrsh {
9544            rd: Reg::R0,
9545            addr: MemAddr::imm(Reg::R1, 0),
9546        };
9547        let code = encoder.encode(&op).unwrap();
9548        assert_eq!(code.len(), 4, "Thumb-2 LDRSH should be 32-bit");
9549    }
9550
9551    #[test]
9552    fn test_encode_memory_size_thumb2() {
9553        let encoder = ArmEncoder::new_thumb2();
9554        let op = ArmOp::MemorySize { rd: Reg::R0 };
9555        let code = encoder.encode(&op).unwrap();
9556        // R0 and R10 are not both low registers, so this needs careful handling
9557        assert!(!code.is_empty(), "MemorySize should produce code");
9558    }
9559
9560    #[test]
9561    fn test_encode_memory_grow_thumb2() {
9562        let encoder = ArmEncoder::new_thumb2();
9563        let op = ArmOp::MemoryGrow {
9564            rd: Reg::R0,
9565            rn: Reg::R0,
9566        };
9567        let code = encoder.encode(&op).unwrap();
9568        assert_eq!(code.len(), 4, "MemoryGrow (MVN) should be 32-bit Thumb-2");
9569    }
9570
9571    #[test]
9572    fn test_encode_subword_reg_offset_thumb2() {
9573        let encoder = ArmEncoder::new_thumb2();
9574
9575        // LDRB with register offset
9576        let op = ArmOp::Ldrb {
9577            rd: Reg::R0,
9578            addr: MemAddr::reg(Reg::R1, Reg::R2),
9579        };
9580        let code = encoder.encode(&op).unwrap();
9581        assert_eq!(
9582            code.len(),
9583            4,
9584            "Thumb-2 LDRB with reg offset should be 32-bit"
9585        );
9586
9587        // STRB with register offset
9588        let op = ArmOp::Strb {
9589            rd: Reg::R0,
9590            addr: MemAddr::reg(Reg::R1, Reg::R2),
9591        };
9592        let code = encoder.encode(&op).unwrap();
9593        assert_eq!(
9594            code.len(),
9595            4,
9596            "Thumb-2 STRB with reg offset should be 32-bit"
9597        );
9598
9599        // LDRH with register offset
9600        let op = ArmOp::Ldrh {
9601            rd: Reg::R0,
9602            addr: MemAddr::reg(Reg::R1, Reg::R2),
9603        };
9604        let code = encoder.encode(&op).unwrap();
9605        assert_eq!(
9606            code.len(),
9607            4,
9608            "Thumb-2 LDRH with reg offset should be 32-bit"
9609        );
9610
9611        // STRH with register offset
9612        let op = ArmOp::Strh {
9613            rd: Reg::R0,
9614            addr: MemAddr::reg(Reg::R1, Reg::R2),
9615        };
9616        let code = encoder.encode(&op).unwrap();
9617        assert_eq!(
9618            code.len(),
9619            4,
9620            "Thumb-2 STRH with reg offset should be 32-bit"
9621        );
9622    }
9623
9624    #[test]
9625    fn test_encode_subword_reg_imm_offset_thumb2() {
9626        let encoder = ArmEncoder::new_thumb2();
9627
9628        // LDRB with both register and immediate offset
9629        let op = ArmOp::Ldrb {
9630            rd: Reg::R0,
9631            addr: MemAddr::reg_imm(Reg::R1, Reg::R2, 4),
9632        };
9633        let code = encoder.encode(&op).unwrap();
9634        // ADD R12, R2, #4 (4 bytes) + LDRB R0, [R1, R12] (4 bytes) = 8 bytes
9635        assert_eq!(
9636            code.len(),
9637            8,
9638            "Thumb-2 LDRB with reg+imm offset should be 8 bytes"
9639        );
9640    }
9641
9642    // ========================================================================
9643    // Helium MVE encoding tests
9644    // ========================================================================
9645
9646    #[test]
9647    fn test_encode_mve_addi32_thumb2() {
9648        let encoder = ArmEncoder::new_thumb2();
9649        let op = ArmOp::MveAddI {
9650            qd: QReg::Q0,
9651            qn: QReg::Q1,
9652            qm: QReg::Q2,
9653            size: MveSize::S32,
9654        };
9655        let code = encoder.encode(&op).unwrap();
9656        assert_eq!(
9657            code.len(),
9658            4,
9659            "MVE VADD.I32 should be 4 bytes (Thumb-2 32-bit)"
9660        );
9661    }
9662
9663    #[test]
9664    fn test_encode_mve_subi16_thumb2() {
9665        let encoder = ArmEncoder::new_thumb2();
9666        let op = ArmOp::MveSubI {
9667            qd: QReg::Q0,
9668            qn: QReg::Q1,
9669            qm: QReg::Q2,
9670            size: MveSize::S16,
9671        };
9672        let code = encoder.encode(&op).unwrap();
9673        assert_eq!(code.len(), 4, "MVE VSUB.I16 should be 4 bytes");
9674    }
9675
9676    #[test]
9677    fn test_encode_mve_muli8_thumb2() {
9678        let encoder = ArmEncoder::new_thumb2();
9679        let op = ArmOp::MveMulI {
9680            qd: QReg::Q0,
9681            qn: QReg::Q1,
9682            qm: QReg::Q2,
9683            size: MveSize::S8,
9684        };
9685        let code = encoder.encode(&op).unwrap();
9686        assert_eq!(code.len(), 4, "MVE VMUL.I8 should be 4 bytes");
9687    }
9688
9689    #[test]
9690    fn test_encode_mve_bitwise_thumb2() {
9691        let encoder = ArmEncoder::new_thumb2();
9692
9693        let ops = vec![
9694            ArmOp::MveAnd {
9695                qd: QReg::Q0,
9696                qn: QReg::Q1,
9697                qm: QReg::Q2,
9698            },
9699            ArmOp::MveOrr {
9700                qd: QReg::Q0,
9701                qn: QReg::Q1,
9702                qm: QReg::Q2,
9703            },
9704            ArmOp::MveEor {
9705                qd: QReg::Q0,
9706                qn: QReg::Q1,
9707                qm: QReg::Q2,
9708            },
9709            ArmOp::MveBic {
9710                qd: QReg::Q0,
9711                qn: QReg::Q1,
9712                qm: QReg::Q2,
9713            },
9714        ];
9715        for op in ops {
9716            let code = encoder.encode(&op).unwrap();
9717            assert_eq!(code.len(), 4, "MVE bitwise op should be 4 bytes");
9718        }
9719    }
9720
9721    #[test]
9722    fn test_encode_mve_mvn_thumb2() {
9723        let encoder = ArmEncoder::new_thumb2();
9724        let op = ArmOp::MveMvn {
9725            qd: QReg::Q0,
9726            qm: QReg::Q1,
9727        };
9728        let code = encoder.encode(&op).unwrap();
9729        assert_eq!(code.len(), 4, "MVE VMVN should be 4 bytes");
9730    }
9731
9732    #[test]
9733    fn test_encode_mve_load_store_thumb2() {
9734        let encoder = ArmEncoder::new_thumb2();
9735
9736        let load = ArmOp::MveLoad {
9737            qd: QReg::Q0,
9738            addr: MemAddr::imm(Reg::R0, 16),
9739        };
9740        let code = encoder.encode(&load).unwrap();
9741        assert_eq!(code.len(), 4, "MVE VLDRW.32 should be 4 bytes");
9742
9743        let store = ArmOp::MveStore {
9744            qd: QReg::Q1,
9745            addr: MemAddr::imm(Reg::R1, 0),
9746        };
9747        let code = encoder.encode(&store).unwrap();
9748        assert_eq!(code.len(), 4, "MVE VSTRW.32 should be 4 bytes");
9749    }
9750
9751    #[test]
9752    fn test_encode_mve_const_thumb2() {
9753        let encoder = ArmEncoder::new_thumb2();
9754        let op = ArmOp::MveConst {
9755            qd: QReg::Q0,
9756            bytes: [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0],
9757        };
9758        let code = encoder.encode(&op).unwrap();
9759        // Should be 4 words of (MOVW R12 + VMOV Sn) = 4 * (4+4) = 32 bytes min
9760        // Some words with hi16=0 skip MOVT, so length varies
9761        assert!(
9762            code.len() >= 24,
9763            "MVE const should produce multiple instructions"
9764        );
9765    }
9766
9767    #[test]
9768    fn test_encode_mve_dup_thumb2() {
9769        let encoder = ArmEncoder::new_thumb2();
9770        let op = ArmOp::MveDup {
9771            qd: QReg::Q0,
9772            rn: Reg::R0,
9773            size: MveSize::S32,
9774        };
9775        let code = encoder.encode(&op).unwrap();
9776        assert_eq!(code.len(), 4, "MVE VDUP.32 should be 4 bytes");
9777    }
9778
9779    #[test]
9780    fn test_encode_mve_extract_lane_thumb2() {
9781        let encoder = ArmEncoder::new_thumb2();
9782        let op = ArmOp::MveExtractLane {
9783            rd: Reg::R0,
9784            qn: QReg::Q1,
9785            lane: 2,
9786            size: MveSize::S32,
9787        };
9788        let code = encoder.encode(&op).unwrap();
9789        assert_eq!(code.len(), 4, "MVE extract lane should be 4 bytes");
9790    }
9791
9792    #[test]
9793    fn test_encode_mve_insert_lane_thumb2() {
9794        let encoder = ArmEncoder::new_thumb2();
9795        let op = ArmOp::MveInsertLane {
9796            qd: QReg::Q0,
9797            rn: Reg::R1,
9798            lane: 3,
9799            size: MveSize::S32,
9800        };
9801        let code = encoder.encode(&op).unwrap();
9802        assert_eq!(code.len(), 4, "MVE insert lane should be 4 bytes");
9803    }
9804
9805    #[test]
9806    fn test_encode_mve_addf32_thumb2() {
9807        let encoder = ArmEncoder::new_thumb2();
9808        let op = ArmOp::MveAddF32 {
9809            qd: QReg::Q0,
9810            qn: QReg::Q1,
9811            qm: QReg::Q2,
9812        };
9813        let code = encoder.encode(&op).unwrap();
9814        assert_eq!(code.len(), 4, "MVE VADD.F32 should be 4 bytes");
9815    }
9816
9817    #[test]
9818    fn test_encode_mve_divf32_thumb2() {
9819        let encoder = ArmEncoder::new_thumb2();
9820        let op = ArmOp::MveDivF32 {
9821            qd: QReg::Q0,
9822            qn: QReg::Q1,
9823            qm: QReg::Q2,
9824        };
9825        let code = encoder.encode(&op).unwrap();
9826        // Lane-wise: 4 x VDIV.F32 = 4 x 4 = 16 bytes
9827        assert_eq!(
9828            code.len(),
9829            16,
9830            "MVE VDIV.F32 (lane-wise) should be 16 bytes"
9831        );
9832    }
9833
9834    #[test]
9835    fn test_encode_mve_sqrtf32_thumb2() {
9836        let encoder = ArmEncoder::new_thumb2();
9837        let op = ArmOp::MveSqrtF32 {
9838            qd: QReg::Q0,
9839            qm: QReg::Q1,
9840        };
9841        let code = encoder.encode(&op).unwrap();
9842        // Lane-wise: 4 x VSQRT.F32 = 4 x 4 = 16 bytes
9843        assert_eq!(
9844            code.len(),
9845            16,
9846            "MVE VSQRT.F32 (lane-wise) should be 16 bytes"
9847        );
9848    }
9849
9850    #[test]
9851    fn test_encode_mve_negf32_thumb2() {
9852        let encoder = ArmEncoder::new_thumb2();
9853        let op = ArmOp::MveNegF32 {
9854            qd: QReg::Q0,
9855            qm: QReg::Q1,
9856        };
9857        let code = encoder.encode(&op).unwrap();
9858        assert_eq!(code.len(), 4, "MVE VNEG.F32 should be 4 bytes");
9859    }
9860
9861    #[test]
9862    fn test_encode_mve_absf32_thumb2() {
9863        let encoder = ArmEncoder::new_thumb2();
9864        let op = ArmOp::MveAbsF32 {
9865            qd: QReg::Q0,
9866            qm: QReg::Q1,
9867        };
9868        let code = encoder.encode(&op).unwrap();
9869        assert_eq!(code.len(), 4, "MVE VABS.F32 should be 4 bytes");
9870    }
9871
9872    /// VCR-RA-001 / immediate-folding precondition: pins the Thumb-2 `AND`
9873    /// immediate encoding for the byte range and documents its bound.
9874    ///
9875    /// The `And { Operand2::Imm }` encoder packs the low 12 bits straight into
9876    /// the `i:imm3:imm8` field WITHOUT applying ThumbExpandImm (the modified-
9877    /// immediate expansion). For `imm <= 0xFF` (e.g. gale's int8 clamps
9878    /// `#0x7e` / `#0x7f`) that is correct — `i:imm3 = 0000` means "imm8
9879    /// zero-extended". So `and r2, r0, #0x7e` encodes to the canonical
9880    /// `00 f0 7e 02`. For `imm >= 0x100` the field would need a true
9881    /// ThumbExpandImm pattern (rotation / replication), which is NOT
9882    /// implemented here — so **immediate folding must gate on `imm <= 0xFF`**
9883    /// until the encoder is hardened to ThumbExpandImm/Ok-or-Err (the
9884    /// "encoder must be Ok-or-Err, never silently wrong" principle, #180/#185).
9885    /// This bound covers the measured `flat_flight` waste (#209).
9886    #[test]
9887    fn and_immediate_encodes_correctly_in_byte_range_documents_fold_bound() {
9888        let encoder = ArmEncoder::new_thumb2();
9889        let op = ArmOp::And {
9890            rd: Reg::R2,
9891            rn: Reg::R0,
9892            op2: Operand2::Imm(0x7e),
9893        };
9894        let code = encoder.encode(&op).unwrap();
9895        assert_eq!(
9896            code,
9897            vec![0x00, 0xf0, 0x7e, 0x02],
9898            "and r2, r0, #0x7e must encode to the canonical AND.W T1 (imm8=0x7e)"
9899        );
9900    }
9901
9902    /// #255: the shared ThumbExpandImm reverse-encoder underpinning the
9903    /// data-processing immediate fix. Encodable modified immediates round-trip to
9904    /// the expected `i:imm3:imm8` field; a genuinely non-modified value is `None`
9905    /// (caller must materialize into a register). Note `1000 = 0xFA ror 30` *is*
9906    /// representable (field 0xF7A) — the old encoder mis-encoded it (raw 0x3E8);
9907    /// this encodes it correctly.
9908    #[test]
9909    fn try_thumb_expand_imm_encodes_modified_immediates() {
9910        assert_eq!(try_thumb_expand_imm(0x7e), Some(0x07e)); // zero-extended byte
9911        assert_eq!(try_thumb_expand_imm(0xff), Some(0x0ff));
9912        assert_eq!(try_thumb_expand_imm(0x0001_0001), Some(0x101)); // 0x00XY00XY
9913        assert_eq!(try_thumb_expand_imm(0xff00_ff00), Some(0x2ff)); // 0xXY00XY00
9914        assert_eq!(try_thumb_expand_imm(0xffff_ffff), Some(0x3ff)); // 0xXYXYXYXY
9915        assert_eq!(try_thumb_expand_imm(0x100), Some(0xf80)); // 0x80 ror 31
9916        assert_eq!(try_thumb_expand_imm(0x8000_0000), Some(0x400)); // 0x80 ror 8
9917        assert_eq!(try_thumb_expand_imm(1000), Some(0xf7a)); // 0xFA ror 30
9918        // Genuinely unrepresentable (bits too far apart for an 8-bit window).
9919        assert_eq!(try_thumb_expand_imm(0x101), None);
9920        assert_eq!(try_thumb_expand_imm(0x12345), None);
9921    }
9922
9923    /// #255: CMP/ADDS/SUBS encode any valid modified immediate correctly, and
9924    /// ERROR (not silently mis-encode) on a genuinely unrepresentable one,
9925    /// forcing the selector to materialize into a register — closing the
9926    /// silent-miscompile class of #251/#253.
9927    #[test]
9928    fn cmp_adds_subs_immediate_error_on_non_modified_imm() {
9929        let encoder = ArmEncoder::new_thumb2();
9930        // cmp r0, #0xff → valid → Ok; cmp r0, #1000 → valid (0xFA ror 30) → Ok.
9931        assert!(encoder.encode_thumb32_cmp_imm(&Reg::R0, 0xff).is_ok());
9932        assert!(encoder.encode_thumb32_cmp_imm(&Reg::R0, 1000).is_ok());
9933        // cmp r0, #0x101 → NOT a modified immediate → Err (materialize-reg).
9934        assert!(
9935            encoder.encode_thumb32_cmp_imm(&Reg::R0, 0x101).is_err(),
9936            "cmp #0x101 must error, not compare the wrong constant"
9937        );
9938        assert!(
9939            encoder
9940                .encode_thumb32_adds(&Reg::R0, &Reg::R0, 0x101)
9941                .is_err()
9942        );
9943        assert!(
9944            encoder
9945                .encode_thumb32_subs(&Reg::R0, &Reg::R0, 0x101)
9946                .is_err()
9947        );
9948        // ...but a valid modified immediate still encodes.
9949        assert!(
9950            encoder
9951                .encode_thumb32_adds(&Reg::R0, &Reg::R0, 0x80)
9952                .is_ok()
9953        );
9954    }
9955
9956    /// #257: MLA (multiply-accumulate) encodes as MLS without the bit-4 op flag.
9957    /// `mla r2, r3, r4, r8` (rd=r2, rn=r3, rm=r4, ra=r8) → Thumb-2 `03 fb 04 82`.
9958    #[test]
9959    fn mla_thumb2_encodes_correctly() {
9960        let encoder = ArmEncoder::new_thumb2();
9961        let code = encoder
9962            .encode(&ArmOp::Mla {
9963                rd: Reg::R2,
9964                rn: Reg::R3,
9965                rm: Reg::R4,
9966                ra: Reg::R8,
9967            })
9968            .unwrap();
9969        // hw1 = 0xFB03, hw2 = (8<<12)|(2<<8)|4 = 0x8204
9970        assert_eq!(code, vec![0x03, 0xfb, 0x04, 0x82]);
9971    }
9972
9973    /// #259: LDR/STR (and sub-word) immediate-offset encoders truncated
9974    /// `offset & 0xFFF`, silently targeting the wrong address for offset >= 4096.
9975    /// They now error (the selector must use register-offset addressing) — the
9976    /// load/store sibling of the #253/#255 class. Offsets <= 4095 still encode.
9977    #[test]
9978    fn ldst_imm12_offset_errors_when_out_of_range() {
9979        let encoder = ArmEncoder::new_thumb2();
9980        // offset 0xFFF (4095): valid → Ok; ldr r0, [r1, #4095].
9981        assert!(
9982            encoder
9983                .encode_thumb32_ldr(&Reg::R0, &Reg::R1, 0xFFF)
9984                .is_ok()
9985        );
9986        // offset 0x1000 (4096): out of imm12 range → Err (not & 0xFFF → #0).
9987        assert!(
9988            encoder
9989                .encode_thumb32_ldr(&Reg::R0, &Reg::R1, 0x1000)
9990                .is_err(),
9991            "ldr offset 4096 must error, not wrap to 0"
9992        );
9993        assert!(
9994            encoder
9995                .encode_thumb32_str(&Reg::R0, &Reg::R1, 0x1000)
9996                .is_err()
9997        );
9998        assert!(
9999            encoder
10000                .encode_thumb32_ldrb_imm(&Reg::R0, &Reg::R1, 5000)
10001                .is_err()
10002        );
10003        assert!(
10004            encoder
10005                .encode_thumb32_strh_imm(&Reg::R0, &Reg::R1, 5000)
10006                .is_err()
10007        );
10008    }
10009
10010    /// Latent miscompile fix: ADD/SUB with a >0xFF immediate (e.g.
10011    /// `add sp, sp, #frame` for a >=256-byte frame) used ADD.W (T3), whose
10012    /// `i:imm3:imm8` is a ThumbExpandImm modified immediate — so `#256` silently
10013    /// encoded as `#0` (stack corruption). Use ADDW/SUBW (T4), a PLAIN 12-bit
10014    /// immediate, for 0x100..=0xFFF; keep T3 for <=0xFF (bit-identical); error
10015    /// beyond 4095.
10016    #[test]
10017    fn add_sub_large_immediate_use_addw_subw_not_misencoded() {
10018        let encoder = ArmEncoder::new_thumb2();
10019        // add sp, sp, #256  →  ADDW (T4) SP, SP, #256  =  0d f2 00 1d
10020        assert_eq!(
10021            encoder
10022                .encode(&ArmOp::Add {
10023                    rd: Reg::SP,
10024                    rn: Reg::SP,
10025                    op2: Operand2::Imm(256),
10026                })
10027                .unwrap(),
10028            vec![0x0d, 0xf2, 0x00, 0x1d],
10029            "add sp,sp,#256 must be ADDW (plain imm12), not a mis-encoded ADD.W"
10030        );
10031        // sub sp, sp, #256  →  SUBW (T4) SP, SP, #256  =  ad f2 00 1d
10032        assert_eq!(
10033            encoder
10034                .encode(&ArmOp::Sub {
10035                    rd: Reg::SP,
10036                    rn: Reg::SP,
10037                    op2: Operand2::Imm(256),
10038                })
10039                .unwrap(),
10040            vec![0xad, 0xf2, 0x00, 0x1d],
10041        );
10042        // > 4095 has no single-instruction encoding → error, not silent wrong.
10043        assert!(
10044            encoder
10045                .encode(&ArmOp::Add {
10046                    rd: Reg::SP,
10047                    rn: Reg::SP,
10048                    op2: Operand2::Imm(5000),
10049                })
10050                .is_err(),
10051            "add #5000 must error (no single ADDW), not mis-encode"
10052        );
10053    }
10054
10055    /// Closes the data-proc immediate class: AND and CMN now go through
10056    /// `try_thumb_expand_imm` like ORR/EOR/CMP — correct for any modified
10057    /// immediate, `Err` (not raw-pack / NOP) on an un-encodable one. The byte
10058    /// range stays bit-identical (`and r2,r0,#0x7e` is unchanged).
10059    #[test]
10060    fn and_cmn_immediate_thumb_expand_else_error() {
10061        let encoder = ArmEncoder::new_thumb2();
10062        // byte range unchanged (bit-identical with the pre-retrofit encoding)
10063        assert_eq!(
10064            encoder
10065                .encode(&ArmOp::And {
10066                    rd: Reg::R2,
10067                    rn: Reg::R0,
10068                    op2: Operand2::Imm(0x7e),
10069                })
10070                .unwrap(),
10071            vec![0x00, 0xf0, 0x7e, 0x02],
10072        );
10073        // a valid replicated modified immediate now encodes (was silently wrong)
10074        assert!(
10075            encoder
10076                .encode(&ArmOp::And {
10077                    rd: Reg::R2,
10078                    rn: Reg::R0,
10079                    op2: Operand2::Imm(0xff00ff00u32 as i32),
10080                })
10081                .is_ok()
10082        );
10083        // a genuinely un-encodable immediate errors (AND was raw-pack; CMN NOP)
10084        assert!(
10085            encoder
10086                .encode(&ArmOp::And {
10087                    rd: Reg::R2,
10088                    rn: Reg::R0,
10089                    op2: Operand2::Imm(0x101),
10090                })
10091                .is_err()
10092        );
10093        assert!(
10094            encoder
10095                .encode(&ArmOp::Cmn {
10096                    rn: Reg::R0,
10097                    op2: Operand2::Imm(0x101),
10098                })
10099                .is_err(),
10100            "CMN #0x101 must error, not emit a NOP"
10101        );
10102    }
10103
10104    /// VCR-RA-001: ORR/EOR with a small immediate must encode the real
10105    /// instruction (not a silent `0xBF00` NOP). Pins the byte range and the
10106    /// Ok-or-Err bound that makes future Or/Eor immediate folding safe.
10107    #[test]
10108    fn orr_eor_immediate_encode_in_byte_range_else_error() {
10109        let encoder = ArmEncoder::new_thumb2();
10110        // orr r2, r0, #0x7e  →  ORR.W T1, imm8=0x7e
10111        assert_eq!(
10112            encoder
10113                .encode(&ArmOp::Orr {
10114                    rd: Reg::R2,
10115                    rn: Reg::R0,
10116                    op2: Operand2::Imm(0x7e),
10117                })
10118                .unwrap(),
10119            vec![0x40, 0xf0, 0x7e, 0x02],
10120        );
10121        // eor r2, r0, #0x7e  →  EOR.W T1, imm8=0x7e
10122        assert_eq!(
10123            encoder
10124                .encode(&ArmOp::Eor {
10125                    rd: Reg::R2,
10126                    rn: Reg::R0,
10127                    op2: Operand2::Imm(0x7e),
10128                })
10129                .unwrap(),
10130            vec![0x80, 0xf0, 0x7e, 0x02],
10131        );
10132        // Out-of-range immediates error rather than silently mis-encode / NOP.
10133        assert!(
10134            encoder
10135                .encode(&ArmOp::Orr {
10136                    rd: Reg::R2,
10137                    rn: Reg::R0,
10138                    op2: Operand2::Imm(0x140),
10139                })
10140                .is_err(),
10141            "ORR #0x140 must error, not emit a NOP"
10142        );
10143    }
10144
10145    #[test]
10146    fn test_encode_mve_different_qregs() {
10147        let encoder = ArmEncoder::new_thumb2();
10148
10149        // Test that different Q-register numbers produce different encodings
10150        let op1 = ArmOp::MveAddI {
10151            qd: QReg::Q0,
10152            qn: QReg::Q0,
10153            qm: QReg::Q0,
10154            size: MveSize::S32,
10155        };
10156        let op2 = ArmOp::MveAddI {
10157            qd: QReg::Q3,
10158            qn: QReg::Q5,
10159            qm: QReg::Q7,
10160            size: MveSize::S32,
10161        };
10162        let code1 = encoder.encode(&op1).unwrap();
10163        let code2 = encoder.encode(&op2).unwrap();
10164        assert_ne!(
10165            code1, code2,
10166            "Different Q-registers should produce different encodings"
10167        );
10168    }
10169
10170    #[test]
10171    fn test_encode_mve_arm32_nop() {
10172        // MVE instructions on ARM32 encoder should produce NOP (only Thumb-2 supported)
10173        let encoder = ArmEncoder::new_arm32();
10174        let op = ArmOp::MveAddI {
10175            qd: QReg::Q0,
10176            qn: QReg::Q1,
10177            qm: QReg::Q2,
10178            size: MveSize::S32,
10179        };
10180        let code = encoder.encode(&op).unwrap();
10181        assert_eq!(code.len(), 4, "ARM32 MVE should be 4 bytes (NOP)");
10182        // NOP in ARM32 is 0xE1A00000 (MOV R0, R0)
10183        let instr = u32::from_le_bytes([code[0], code[1], code[2], code[3]]);
10184        assert_eq!(instr, 0xE1A00000, "ARM32 MVE should encode as NOP");
10185    }
10186}