Skip to main content

pcode_ir/
lib.rs

1//! P-code intermediate representation types.
2//!
3//! Zero-dependency crate defining [`PcodeOp`] and [`Varnode`] — the types
4//! emitted by SLEIGH-generated instruction decoders.
5
6#![no_std]
7
8extern crate alloc;
9use alloc::collections::{BTreeMap, BTreeSet};
10use alloc::string::String;
11use alloc::vec;
12use alloc::vec::Vec;
13
14/// Identifies an address space in the P-code model.
15///
16/// `Ord` is derived for use as `BTreeMap` keys in the peephole optimizer (no_std
17/// precludes `HashMap`). The derived ordering is arbitrary and should not be relied
18/// upon for semantic comparisons.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
20pub enum AddressSpaceId {
21    /// CPU registers (offset = Ghidra register offset).
22    Register,
23    /// Main memory / RAM.
24    Ram,
25    /// Temporary storage (unique per instruction lift).
26    Unique,
27    /// Constants (offset = the constant value itself).
28    Const,
29}
30
31/// A triple (space, offset, size) identifying a storage location or constant.
32///
33/// `Ord` is derived for use as `BTreeMap` keys in the peephole optimizer.
34/// The derived lexicographic ordering is not semantically meaningful.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
36pub struct Varnode {
37    pub space: AddressSpaceId,
38    pub offset: u64,
39    /// Size in bytes.
40    pub size: u32,
41}
42
43impl Varnode {
44    /// A CPU register at the given Ghidra offset.
45    #[inline]
46    pub fn register(offset: u64, size: u32) -> Self {
47        Self {
48            space: AddressSpaceId::Register,
49            offset,
50            size,
51        }
52    }
53
54    /// A unique (temporary) varnode.
55    #[inline]
56    pub fn unique(offset: u64, size: u32) -> Self {
57        Self {
58            space: AddressSpaceId::Unique,
59            offset,
60            size,
61        }
62    }
63
64    /// A RAM location.
65    #[inline]
66    pub fn ram(offset: u64, size: u32) -> Self {
67        Self {
68            space: AddressSpaceId::Ram,
69            offset,
70            size,
71        }
72    }
73
74    /// A constant value encoded as a varnode.
75    #[inline]
76    pub fn constant(value: u64, size: u32) -> Self {
77        Self {
78            space: AddressSpaceId::Const,
79            offset: value,
80            size,
81        }
82    }
83}
84
85/// Provenance metadata identifying which SLEIGH constructor produced an
86/// `Instruction`. Audit P2 #3 — gives the analyst a way to trace
87/// surprising P-code back to its source rule when debugging lifter
88/// divergences. `None` means the producing decoder pre-dated this
89/// metadata (legacy generated crates) or the decoder declined to emit it.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub struct ConstructorSpan {
92    /// Numeric constructor index assigned by the SLEIGH compiler.
93    pub constructor_id: u32,
94    /// Numeric table id the constructor belongs to.
95    pub table_id: u32,
96    /// Free-form source location string (file:line) emitted by codegen.
97    /// Empty string when the codegen did not record one.
98    pub source: &'static str,
99}
100
101/// A decoded and lifted instruction.
102#[derive(Debug, Clone)]
103pub struct Instruction {
104    /// Number of bytes consumed by this instruction.
105    pub len: u64,
106    /// Human-readable disassembly (e.g. "MOV RAX,RBX").
107    pub disassembly: String,
108    /// P-code operations (peephole-optimized).
109    pub ops: Vec<PcodeOp>,
110    /// Optional SLEIGH constructor that emitted this instruction.
111    /// Default `None` for backward compatibility with generated crates
112    /// that don't yet populate this field.
113    pub constructor: Option<ConstructorSpan>,
114}
115
116impl Instruction {
117    /// Construct an instruction without constructor provenance —
118    /// preserves call sites that pre-date the `constructor` field.
119    pub fn new(len: u64, disassembly: String, ops: Vec<PcodeOp>) -> Self {
120        Self {
121            len,
122            disassembly,
123            ops,
124            constructor: None,
125        }
126    }
127}
128
129/// Decode error returned when an instruction cannot be parsed.
130#[derive(Debug, Clone)]
131pub enum DecodeError {
132    /// The byte sequence does not match any known instruction encoding.
133    UnknownInstruction,
134}
135
136impl core::fmt::Display for DecodeError {
137    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
138        match self {
139            DecodeError::UnknownInstruction => write!(f, "unknown instruction encoding"),
140        }
141    }
142}
143
144/// Offset all Unique-space varnode offsets in an op by the given amount.
145/// Used to avoid unique offset collisions when combining P-code from multiple subtables.
146pub fn offset_unique_varnodes(op: &mut PcodeOp, offset: u64) {
147    // Offset outputs
148    if let Some(out) = get_output_mut(op) {
149        if out.space == AddressSpaceId::Unique {
150            out.offset += offset;
151        }
152    }
153    // Offset inputs
154    visit_reads_mut(op, &mut |v| {
155        if v.space == AddressSpaceId::Unique {
156            v.offset += offset;
157        }
158    });
159}
160
161/// Peephole-optimize a P-code op sequence:
162/// - Fold small constant integer/logical expressions.
163/// - Remove identity `Subpiece { lsb: 0 }` where input.size == out.size.
164/// - Forward-substitute `Copy` chains (A=B, C=A → C=B) when the
165///   intermediate is a unique varnode used only once after its definition.
166pub fn optimize(ops: &mut Vec<PcodeOp>) {
167    // Run passes until fixpoint (later passes create opportunities for earlier ones)
168    for _round in 0..4 {
169        let before = ops.len();
170        optimize_once(ops);
171        if ops.len() == before {
172            break;
173        }
174    }
175}
176
177fn optimize_once(ops: &mut Vec<PcodeOp>) {
178    // Pass 0: constant folding. Keep this intentionally small: P-code constants
179    // are u64-backed, so only fold values whose input/output widths fit.
180    for op in ops.iter_mut() {
181        if let Some((out, value)) = const_fold_op(op) {
182            *op = PcodeOp::Copy {
183                out,
184                input: Varnode::constant(value, out.size),
185            };
186            continue;
187        }
188
189        match op {
190            PcodeOp::IntZext { out, input } if input.space == AddressSpaceId::Const => {
191                *op = PcodeOp::Copy {
192                    out: *out,
193                    input: Varnode::constant(mask_to_size(input.offset, out.size), out.size),
194                };
195            }
196            PcodeOp::IntSext { out, input } if input.space == AddressSpaceId::Const => {
197                *op = PcodeOp::Copy {
198                    out: *out,
199                    input: Varnode::constant(
200                        sign_extend_to_size(input.offset, input.size, out.size),
201                        out.size,
202                    ),
203                };
204            }
205            // Shift by zero → Copy
206            PcodeOp::IntLsr { out, left, right }
207            | PcodeOp::IntLsl { out, left, right }
208            | PcodeOp::IntAsr { out, left, right }
209                if right.space == AddressSpaceId::Const && right.offset == 0 =>
210            {
211                *op = PcodeOp::Copy {
212                    out: *out,
213                    input: *left,
214                };
215            }
216            // OR with zero → Copy
217            PcodeOp::IntOr { out, left, right }
218                if right.space == AddressSpaceId::Const && right.offset == 0 =>
219            {
220                *op = PcodeOp::Copy {
221                    out: *out,
222                    input: *left,
223                };
224            }
225            PcodeOp::IntOr { out, left, right }
226                if left.space == AddressSpaceId::Const && left.offset == 0 =>
227            {
228                *op = PcodeOp::Copy {
229                    out: *out,
230                    input: *right,
231                };
232            }
233            // AND with all-ones (for the size) → Copy
234            PcodeOp::IntAnd { out, left, right }
235                if right.space == AddressSpaceId::Const
236                    && right.offset == all_ones_mask(out.size) =>
237            {
238                *op = PcodeOp::Copy {
239                    out: *out,
240                    input: *left,
241                };
242            }
243            _ => {}
244        }
245    }
246
247    // Pass 1: eliminate identity Subpiece
248    for op in ops.iter_mut() {
249        if let PcodeOp::Subpiece { out, input, lsb: 0 } = op {
250            if out.size == input.size {
251                *op = PcodeOp::Copy {
252                    out: *out,
253                    input: *input,
254                };
255            }
256        }
257    }
258
259    // Pass 1b: collapse `Subpiece{Const(v, big), lsb=0}` → `Copy{Const(v', out.size)}`.
260    // ARM32 `mov rN, #imm8` lifts to a size-8 Const followed by Subpiece-to-4;
261    // Ghidra emits a single Copy of a size-4 Const. Mask the value to the
262    // output width so the truncation is explicit and the Const carries the
263    // correct size for downstream consumers.
264    for op in ops.iter_mut() {
265        if let PcodeOp::Subpiece { out, input, lsb: 0 } = op {
266            if input.space == AddressSpaceId::Const && input.size > out.size {
267                let mask: u64 = if out.size >= 8 {
268                    u64::MAX
269                } else {
270                    (1u64 << (out.size as u64 * 8)) - 1
271                };
272                let truncated = Varnode {
273                    space: AddressSpaceId::Const,
274                    offset: input.offset & mask,
275                    size: out.size,
276                };
277                *op = PcodeOp::Copy {
278                    out: *out,
279                    input: truncated,
280                };
281            }
282        }
283    }
284
285    // Compute unique-varnode analysis once. Passes below share and recompute only
286    // after structural mutations (removals). Indices in NextAccess::Read are
287    // invalidated by any removal, so we must recompute after each ops.remove().
288    let mut analysis = analyze_unique_outputs(ops);
289
290    // Pass 1a: redundant IntAnd — if IntAnd{out1, x, mask} followed by IntAnd{out2, out1, mask}
291    // with same mask and out1 used only once, remove the second
292    let mut i = 0;
293    while i + 1 < ops.len() {
294        let collapse = if let PcodeOp::IntAnd {
295            out: out1,
296            left: _,
297            right: mask1,
298        } = &ops[i]
299        {
300            if out1.space == AddressSpaceId::Unique && mask1.space == AddressSpaceId::Const {
301                if let PcodeOp::IntAnd {
302                    out: out2,
303                    left: in2,
304                    right: mask2,
305                } = &ops[i + 1]
306                {
307                    if *in2 == *out1 && *mask2 == *mask1 {
308                        // out1 is Unique (checked above), so analysis[i] is always Some.
309                        // Use pattern match to be explicit rather than unwrap_or which
310                        // would silently treat non-Unique outputs as zero reads.
311                        let total_reads = match analysis.get(i) {
312                            Some(Some(info)) => info.future_reads,
313                            _ => usize::MAX, // conservative: don't collapse if unknown
314                        };
315                        if total_reads == 1 {
316                            Some(*out2)
317                        } else {
318                            None
319                        }
320                    } else {
321                        None
322                    }
323                } else {
324                    None
325                }
326            } else {
327                None
328            }
329        } else {
330            None
331        };
332
333        if let Some(new_out) = collapse {
334            if let PcodeOp::IntAnd { out, .. } = &mut ops[i] {
335                *out = new_out;
336            }
337            ops.remove(i + 1);
338            analysis = analyze_unique_outputs(ops);
339            continue;
340        }
341        i += 1;
342    }
343
344    // Pass 2: forward-substitute single-use Copy chains.
345    // If ops[i] is Copy { out: A, input: B } and A is Unique,
346    // and exactly one later op reads A, replace that read with B.
347    // Analysis is still valid from pass 1a (no in-place mutations between).
348    let mut i = 0;
349    while i < ops.len() {
350        if let PcodeOp::Copy { out, input } = &ops[i] {
351            if out.space == AddressSpaceId::Unique {
352                let target = *out;
353                let replacement = *input;
354                let entry = analysis.get(i).and_then(|entry| entry.as_ref());
355                if let Some(UniqueOutputInfo {
356                    future_reads: 1,
357                    next_access: Some(NextAccess::Read(read_idx)),
358                }) = entry
359                {
360                    replace_reads(&mut ops[*read_idx], &target, &replacement);
361                    ops.remove(i);
362                    analysis = analyze_unique_outputs(ops);
363                    continue;
364                }
365            }
366        }
367        i += 1;
368    }
369
370    // Pass 2b + 3: overwrite elimination and dead code elimination.
371    // Remove ops that write to Unique varnodes that are either overwritten before
372    // any read (2b) or never read at all (3). These removals are independent —
373    // removing a dead/overwritten op cannot make another op live — so we collect
374    // all indices in one scan and batch-remove in reverse order.
375    // Analysis is still valid from pass 2 (no in-place mutations between).
376    {
377        let mut to_remove = Vec::new();
378        for (i, entry) in analysis.iter().enumerate() {
379            if let Some(info) = entry {
380                if info.future_reads == 0 || matches!(info.next_access, Some(NextAccess::Write)) {
381                    to_remove.push(i);
382                }
383            }
384        }
385        for &idx in to_remove.iter().rev() {
386            ops.remove(idx);
387        }
388    }
389
390    // Pass 4: sink unique outputs into subsequent Copy destinations.
391    // If ops[i] writes to Unique(X) and some later ops[j] is Copy { out: dest, input: Unique(X) },
392    // and Unique(X) is read exactly once (by that Copy), and dest is not written or read between
393    // i and j, rewrite ops[i] to output directly to dest and remove the Copy.
394    // Must recompute — batch removal above invalidated indices.
395    analysis = analyze_unique_outputs(ops);
396    let mut i = 0;
397    while i < ops.len() {
398        let should_sink = match (
399            get_output(&ops[i]),
400            analysis.get(i).and_then(|entry| entry.as_ref()),
401        ) {
402            (
403                Some(out),
404                Some(UniqueOutputInfo {
405                    future_reads: 1,
406                    next_access: Some(NextAccess::Read(copy_idx)),
407                }),
408            ) if out.space == AddressSpaceId::Unique => {
409                if let PcodeOp::Copy {
410                    out: copy_dest,
411                    input: copy_src,
412                } = &ops[*copy_idx]
413                {
414                    if *copy_src == out {
415                        let d = *copy_dest;
416                        let safe = (i + 1..*copy_idx)
417                            .all(|k| count_reads(&ops[k], &d) == 0 && !writes_to(&ops[k], &d));
418                        if safe {
419                            Some((*copy_idx, d))
420                        } else {
421                            None
422                        }
423                    } else {
424                        None
425                    }
426                } else {
427                    None
428                }
429            }
430            _ => None,
431        };
432
433        if let Some((copy_idx, new_dest)) = should_sink {
434            if let Some(out) = get_output_mut(&mut ops[i]) {
435                *out = new_dest;
436            }
437            ops.remove(copy_idx);
438            analysis = analyze_unique_outputs(ops);
439            continue;
440        }
441        i += 1;
442    }
443}
444
445fn all_ones_mask(size_bytes: u32) -> u64 {
446    let bits = size_bytes.saturating_mul(8);
447    if bits >= 64 {
448        u64::MAX
449    } else {
450        u64::MAX >> (64 - bits)
451    }
452}
453
454fn mask_to_size(value: u64, size_bytes: u32) -> u64 {
455    value & all_ones_mask(size_bytes)
456}
457
458fn bits_for_size(size_bytes: u32) -> u32 {
459    size_bytes.saturating_mul(8)
460}
461
462fn foldable_width(size_bytes: u32) -> bool {
463    (1..=8).contains(&size_bytes)
464}
465
466fn signed_value(value: u64, size_bytes: u32) -> i64 {
467    let bits = bits_for_size(size_bytes);
468    if bits >= 64 {
469        value as i64
470    } else {
471        let shift = 64 - bits;
472        ((value << shift) as i64) >> shift
473    }
474}
475
476fn sign_extend_to_size(value: u64, input_size: u32, output_size: u32) -> u64 {
477    if !foldable_width(input_size) || !foldable_width(output_size) {
478        return value;
479    }
480    mask_to_size(signed_value(value, input_size) as u64, output_size)
481}
482
483fn const_fold_op(op: &PcodeOp) -> Option<(Varnode, u64)> {
484    match *op {
485        PcodeOp::IntZext { out, input } if input.space == AddressSpaceId::Const => {
486            fold_unary_const(out, input, |value, _| value)
487        }
488        PcodeOp::IntSext { out, input } if input.space == AddressSpaceId::Const => {
489            if foldable_width(input.size) && foldable_width(out.size) {
490                Some((out, sign_extend_to_size(input.offset, input.size, out.size)))
491            } else {
492                None
493            }
494        }
495        PcodeOp::IntNeg { out, input } if input.space == AddressSpaceId::Const => {
496            fold_unary_const(out, input, |value, size| {
497                value.wrapping_neg() & all_ones_mask(size)
498            })
499        }
500        PcodeOp::IntNot { out, input } if input.space == AddressSpaceId::Const => {
501            fold_unary_const(out, input, |value, size| !value & all_ones_mask(size))
502        }
503        PcodeOp::BoolNot { out, input } if input.space == AddressSpaceId::Const => {
504            fold_unary_const(out, input, |value, _| u64::from(value == 0))
505        }
506        PcodeOp::IntAdd { out, left, right }
507            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
508        {
509            fold_binary_const(out, left, right, |a, b, size| {
510                a.wrapping_add(b) & all_ones_mask(size)
511            })
512        }
513        PcodeOp::IntSub { out, left, right }
514            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
515        {
516            fold_binary_const(out, left, right, |a, b, size| {
517                a.wrapping_sub(b) & all_ones_mask(size)
518            })
519        }
520        PcodeOp::IntMult { out, left, right }
521            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
522        {
523            fold_binary_const(out, left, right, |a, b, size| {
524                a.wrapping_mul(b) & all_ones_mask(size)
525            })
526        }
527        PcodeOp::IntAnd { out, left, right }
528            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
529        {
530            fold_binary_const(out, left, right, |a, b, size| (a & b) & all_ones_mask(size))
531        }
532        PcodeOp::IntOr { out, left, right }
533            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
534        {
535            fold_binary_const(out, left, right, |a, b, size| (a | b) & all_ones_mask(size))
536        }
537        PcodeOp::IntXor { out, left, right }
538            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
539        {
540            fold_binary_const(out, left, right, |a, b, size| (a ^ b) & all_ones_mask(size))
541        }
542        PcodeOp::IntLsl { out, left, right }
543            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
544        {
545            fold_shift_const(out, left, right, |value, shift, size| {
546                if shift >= bits_for_size(size) {
547                    0
548                } else {
549                    (value << shift) & all_ones_mask(size)
550                }
551            })
552        }
553        PcodeOp::IntLsr { out, left, right }
554            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
555        {
556            fold_shift_const(out, left, right, |value, shift, size| {
557                if shift >= bits_for_size(size) {
558                    0
559                } else {
560                    (value >> shift) & all_ones_mask(size)
561                }
562            })
563        }
564        PcodeOp::IntAsr { out, left, right }
565            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
566        {
567            fold_shift_const(out, left, right, |value, shift, size| {
568                if shift >= bits_for_size(size) {
569                    u64::from(signed_value(value, size) < 0) * all_ones_mask(size)
570                } else {
571                    mask_to_size((signed_value(value, size) >> shift) as u64, size)
572                }
573            })
574        }
575        PcodeOp::IntEq { out, left, right }
576            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
577        {
578            fold_compare_const(out, left, right, |a, b, _| a == b)
579        }
580        PcodeOp::IntNotEq { out, left, right }
581            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
582        {
583            fold_compare_const(out, left, right, |a, b, _| a != b)
584        }
585        PcodeOp::IntLess { out, left, right }
586            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
587        {
588            fold_compare_const(out, left, right, |a, b, _| a < b)
589        }
590        PcodeOp::IntLessEq { out, left, right }
591            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
592        {
593            fold_compare_const(out, left, right, |a, b, _| a <= b)
594        }
595        PcodeOp::IntSLess { out, left, right }
596            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
597        {
598            fold_compare_const(out, left, right, |a, b, size| {
599                signed_value(a, size) < signed_value(b, size)
600            })
601        }
602        PcodeOp::IntSLessEq { out, left, right }
603            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
604        {
605            fold_compare_const(out, left, right, |a, b, size| {
606                signed_value(a, size) <= signed_value(b, size)
607            })
608        }
609        PcodeOp::BoolAnd { out, left, right }
610            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
611        {
612            fold_binary_const(out, left, right, |a, b, _| u64::from(a != 0 && b != 0))
613        }
614        PcodeOp::BoolOr { out, left, right }
615            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
616        {
617            fold_binary_const(out, left, right, |a, b, _| u64::from(a != 0 || b != 0))
618        }
619        PcodeOp::BoolXor { out, left, right }
620            if left.space == AddressSpaceId::Const && right.space == AddressSpaceId::Const =>
621        {
622            fold_binary_const(out, left, right, |a, b, _| u64::from((a != 0) ^ (b != 0)))
623        }
624        _ => None,
625    }
626}
627
628fn fold_unary_const(
629    out: Varnode,
630    input: Varnode,
631    f: impl FnOnce(u64, u32) -> u64,
632) -> Option<(Varnode, u64)> {
633    if !foldable_width(out.size) || !foldable_width(input.size) {
634        return None;
635    }
636    Some((
637        out,
638        mask_to_size(
639            f(mask_to_size(input.offset, input.size), input.size),
640            out.size,
641        ),
642    ))
643}
644
645fn fold_binary_const(
646    out: Varnode,
647    left: Varnode,
648    right: Varnode,
649    f: impl FnOnce(u64, u64, u32) -> u64,
650) -> Option<(Varnode, u64)> {
651    if !foldable_width(out.size) || !foldable_width(left.size) || !foldable_width(right.size) {
652        return None;
653    }
654    let size = left.size.max(right.size);
655    let value = f(
656        mask_to_size(left.offset, left.size),
657        mask_to_size(right.offset, right.size),
658        size,
659    );
660    Some((out, mask_to_size(value, out.size)))
661}
662
663fn fold_shift_const(
664    out: Varnode,
665    left: Varnode,
666    right: Varnode,
667    f: impl FnOnce(u64, u32, u32) -> u64,
668) -> Option<(Varnode, u64)> {
669    if !foldable_width(out.size) || !foldable_width(left.size) || !foldable_width(right.size) {
670        return None;
671    }
672    let shift = right.offset.min(u32::MAX as u64) as u32;
673    Some((
674        out,
675        mask_to_size(
676            f(mask_to_size(left.offset, left.size), shift, left.size),
677            out.size,
678        ),
679    ))
680}
681
682fn fold_compare_const(
683    out: Varnode,
684    left: Varnode,
685    right: Varnode,
686    f: impl FnOnce(u64, u64, u32) -> bool,
687) -> Option<(Varnode, u64)> {
688    if !foldable_width(out.size) || !foldable_width(left.size) || !foldable_width(right.size) {
689        return None;
690    }
691    let size = left.size.max(right.size);
692    let result = f(
693        mask_to_size(left.offset, left.size),
694        mask_to_size(right.offset, right.size),
695        size,
696    );
697    Some((out, u64::from(result)))
698}
699
700#[derive(Clone, Copy)]
701struct UniqueOutputInfo {
702    future_reads: usize,
703    next_access: Option<NextAccess>,
704}
705
706#[derive(Clone, Copy)]
707enum NextAccess {
708    Read(usize),
709    Write,
710}
711
712fn analyze_unique_outputs(ops: &[PcodeOp]) -> Vec<Option<UniqueOutputInfo>> {
713    let mut future_reads = BTreeMap::<Varnode, usize>::new();
714    let mut next_access = BTreeMap::<Varnode, NextAccess>::new();
715    let mut result = vec![None; ops.len()];
716
717    // Pre-scan: find Unique varnodes that are written on both sides of an
718    // intra-instruction CBranch (Const-space dest). These are conditional-select
719    // patterns (AArch64 CSEL/CSINC/CNEG) where both writes are live because
720    // the branch may skip the second write. For these varnodes, the backward
721    // analysis must not clear future_reads or mark next_access as Write.
722    let mut cbranch_protected = BTreeSet::<Varnode>::new();
723    {
724        // Find CBranch indices
725        let cbranch_indices: Vec<usize> = ops.iter().enumerate()
726            .filter(|(_, op)| matches!(op, PcodeOp::CBranch { dest, .. } if dest.space == AddressSpaceId::Const))
727            .map(|(i, _)| i)
728            .collect();
729        for &cb_idx in &cbranch_indices {
730            // Collect Unique varnodes written before and after this CBranch
731            let mut before = BTreeSet::new();
732            let mut after = BTreeSet::new();
733            for (i, op) in ops.iter().enumerate() {
734                if let Some(out) = get_output(op) {
735                    if out.space == AddressSpaceId::Unique {
736                        if i < cb_idx {
737                            before.insert(out);
738                        } else if i > cb_idx {
739                            after.insert(out);
740                        }
741                    }
742                }
743            }
744            for v in before.intersection(&after) {
745                cbranch_protected.insert(*v);
746            }
747        }
748    }
749
750    for (idx, op) in ops.iter().enumerate().rev() {
751        if let Some(out) = get_output(op).filter(|out| out.space == AddressSpaceId::Unique) {
752            if cbranch_protected.contains(&out) {
753                // This varnode is written on both sides of a CBranch — both writes
754                // are potentially live. Return None so no pass touches either write.
755                result[idx] = None;
756            } else {
757                result[idx] = Some(UniqueOutputInfo {
758                    future_reads: future_reads.get(&out).copied().unwrap_or(0),
759                    next_access: next_access.get(&out).copied(),
760                });
761                future_reads.remove(&out);
762                next_access.insert(out, NextAccess::Write);
763            }
764        }
765
766        visit_reads(op, &mut |v| {
767            if v.space == AddressSpaceId::Unique {
768                *future_reads.entry(*v).or_insert(0) += 1;
769                next_access.insert(*v, NextAccess::Read(idx));
770            }
771        });
772    }
773
774    result
775}
776
777pub fn get_output(op: &PcodeOp) -> Option<Varnode> {
778    match op {
779        PcodeOp::Copy { out, .. }
780        | PcodeOp::Load { out, .. }
781        | PcodeOp::Subpiece { out, .. }
782        | PcodeOp::IntNeg { out, .. }
783        | PcodeOp::IntNot { out, .. }
784        | PcodeOp::IntZext { out, .. }
785        | PcodeOp::IntSext { out, .. }
786        | PcodeOp::BoolNot { out, .. }
787        | PcodeOp::FloatNeg { out, .. }
788        | PcodeOp::FloatAbs { out, .. }
789        | PcodeOp::FloatSqrt { out, .. }
790        | PcodeOp::FloatNan { out, .. }
791        | PcodeOp::Int2Float { out, .. }
792        | PcodeOp::Float2Float { out, .. }
793        | PcodeOp::Trunc { out, .. }
794        | PcodeOp::FloatCeil { out, .. }
795        | PcodeOp::FloatFloor { out, .. }
796        | PcodeOp::FloatRound { out, .. }
797        | PcodeOp::Popcount { out, .. }
798        | PcodeOp::Lzcount { out, .. }
799        | PcodeOp::IntAdd { out, .. }
800        | PcodeOp::IntSub { out, .. }
801        | PcodeOp::IntMult { out, .. }
802        | PcodeOp::IntDiv { out, .. }
803        | PcodeOp::IntSDiv { out, .. }
804        | PcodeOp::IntRem { out, .. }
805        | PcodeOp::IntSRem { out, .. }
806        | PcodeOp::IntEq { out, .. }
807        | PcodeOp::IntNotEq { out, .. }
808        | PcodeOp::IntLess { out, .. }
809        | PcodeOp::IntLessEq { out, .. }
810        | PcodeOp::IntSLess { out, .. }
811        | PcodeOp::IntSLessEq { out, .. }
812        | PcodeOp::IntAnd { out, .. }
813        | PcodeOp::IntOr { out, .. }
814        | PcodeOp::IntXor { out, .. }
815        | PcodeOp::IntLsl { out, .. }
816        | PcodeOp::IntLsr { out, .. }
817        | PcodeOp::IntAsr { out, .. }
818        | PcodeOp::IntCarry { out, .. }
819        | PcodeOp::IntSCarry { out, .. }
820        | PcodeOp::IntSBorrow { out, .. }
821        | PcodeOp::BoolAnd { out, .. }
822        | PcodeOp::BoolOr { out, .. }
823        | PcodeOp::BoolXor { out, .. }
824        | PcodeOp::FloatAdd { out, .. }
825        | PcodeOp::FloatSub { out, .. }
826        | PcodeOp::FloatMult { out, .. }
827        | PcodeOp::FloatDiv { out, .. }
828        | PcodeOp::FloatEq { out, .. }
829        | PcodeOp::FloatNotEq { out, .. }
830        | PcodeOp::FloatLess { out, .. }
831        | PcodeOp::FloatLessEq { out, .. } => Some(*out),
832        _ => None,
833    }
834}
835
836fn get_output_mut(op: &mut PcodeOp) -> Option<&mut Varnode> {
837    match op {
838        PcodeOp::Copy { out, .. }
839        | PcodeOp::Load { out, .. }
840        | PcodeOp::Subpiece { out, .. }
841        | PcodeOp::IntNeg { out, .. }
842        | PcodeOp::IntNot { out, .. }
843        | PcodeOp::IntZext { out, .. }
844        | PcodeOp::IntSext { out, .. }
845        | PcodeOp::BoolNot { out, .. }
846        | PcodeOp::FloatNeg { out, .. }
847        | PcodeOp::FloatAbs { out, .. }
848        | PcodeOp::FloatSqrt { out, .. }
849        | PcodeOp::FloatNan { out, .. }
850        | PcodeOp::Int2Float { out, .. }
851        | PcodeOp::Float2Float { out, .. }
852        | PcodeOp::Trunc { out, .. }
853        | PcodeOp::FloatCeil { out, .. }
854        | PcodeOp::FloatFloor { out, .. }
855        | PcodeOp::FloatRound { out, .. }
856        | PcodeOp::Popcount { out, .. }
857        | PcodeOp::Lzcount { out, .. }
858        | PcodeOp::IntAdd { out, .. }
859        | PcodeOp::IntSub { out, .. }
860        | PcodeOp::IntMult { out, .. }
861        | PcodeOp::IntDiv { out, .. }
862        | PcodeOp::IntSDiv { out, .. }
863        | PcodeOp::IntRem { out, .. }
864        | PcodeOp::IntSRem { out, .. }
865        | PcodeOp::IntEq { out, .. }
866        | PcodeOp::IntNotEq { out, .. }
867        | PcodeOp::IntLess { out, .. }
868        | PcodeOp::IntLessEq { out, .. }
869        | PcodeOp::IntSLess { out, .. }
870        | PcodeOp::IntSLessEq { out, .. }
871        | PcodeOp::IntAnd { out, .. }
872        | PcodeOp::IntOr { out, .. }
873        | PcodeOp::IntXor { out, .. }
874        | PcodeOp::IntLsl { out, .. }
875        | PcodeOp::IntLsr { out, .. }
876        | PcodeOp::IntAsr { out, .. }
877        | PcodeOp::IntCarry { out, .. }
878        | PcodeOp::IntSCarry { out, .. }
879        | PcodeOp::IntSBorrow { out, .. }
880        | PcodeOp::BoolAnd { out, .. }
881        | PcodeOp::BoolOr { out, .. }
882        | PcodeOp::BoolXor { out, .. }
883        | PcodeOp::FloatAdd { out, .. }
884        | PcodeOp::FloatSub { out, .. }
885        | PcodeOp::FloatMult { out, .. }
886        | PcodeOp::FloatDiv { out, .. }
887        | PcodeOp::FloatEq { out, .. }
888        | PcodeOp::FloatNotEq { out, .. }
889        | PcodeOp::FloatLess { out, .. }
890        | PcodeOp::FloatLessEq { out, .. } => Some(out),
891        _ => None,
892    }
893}
894
895pub fn writes_to(op: &PcodeOp, target: &Varnode) -> bool {
896    match op {
897        PcodeOp::Copy { out, .. }
898        | PcodeOp::Load { out, .. }
899        | PcodeOp::Subpiece { out, .. }
900        | PcodeOp::IntNeg { out, .. }
901        | PcodeOp::IntNot { out, .. }
902        | PcodeOp::IntZext { out, .. }
903        | PcodeOp::IntSext { out, .. }
904        | PcodeOp::BoolNot { out, .. }
905        | PcodeOp::FloatNeg { out, .. }
906        | PcodeOp::FloatAbs { out, .. }
907        | PcodeOp::FloatSqrt { out, .. }
908        | PcodeOp::FloatNan { out, .. }
909        | PcodeOp::Int2Float { out, .. }
910        | PcodeOp::Float2Float { out, .. }
911        | PcodeOp::Trunc { out, .. }
912        | PcodeOp::FloatCeil { out, .. }
913        | PcodeOp::FloatFloor { out, .. }
914        | PcodeOp::FloatRound { out, .. }
915        | PcodeOp::Popcount { out, .. }
916        | PcodeOp::Lzcount { out, .. }
917        | PcodeOp::IntAdd { out, .. }
918        | PcodeOp::IntSub { out, .. }
919        | PcodeOp::IntMult { out, .. }
920        | PcodeOp::IntDiv { out, .. }
921        | PcodeOp::IntSDiv { out, .. }
922        | PcodeOp::IntRem { out, .. }
923        | PcodeOp::IntSRem { out, .. }
924        | PcodeOp::IntEq { out, .. }
925        | PcodeOp::IntNotEq { out, .. }
926        | PcodeOp::IntLess { out, .. }
927        | PcodeOp::IntLessEq { out, .. }
928        | PcodeOp::IntSLess { out, .. }
929        | PcodeOp::IntSLessEq { out, .. }
930        | PcodeOp::IntAnd { out, .. }
931        | PcodeOp::IntOr { out, .. }
932        | PcodeOp::IntXor { out, .. }
933        | PcodeOp::IntLsl { out, .. }
934        | PcodeOp::IntLsr { out, .. }
935        | PcodeOp::IntAsr { out, .. }
936        | PcodeOp::IntCarry { out, .. }
937        | PcodeOp::IntSCarry { out, .. }
938        | PcodeOp::IntSBorrow { out, .. }
939        | PcodeOp::BoolAnd { out, .. }
940        | PcodeOp::BoolOr { out, .. }
941        | PcodeOp::BoolXor { out, .. }
942        | PcodeOp::FloatAdd { out, .. }
943        | PcodeOp::FloatSub { out, .. }
944        | PcodeOp::FloatMult { out, .. }
945        | PcodeOp::FloatDiv { out, .. }
946        | PcodeOp::FloatEq { out, .. }
947        | PcodeOp::FloatNotEq { out, .. }
948        | PcodeOp::FloatLess { out, .. }
949        | PcodeOp::FloatLessEq { out, .. } => out == target,
950        PcodeOp::CallOther { out: Some(out), .. } => out == target,
951        _ => false,
952    }
953}
954
955/// Check if a P-code op reads from a specific varnode.
956pub fn reads_varnode(op: &PcodeOp, target: &Varnode) -> bool {
957    count_reads(op, target) > 0
958}
959
960pub fn count_reads(op: &PcodeOp, target: &Varnode) -> usize {
961    let mut n = 0;
962    visit_reads(op, &mut |v| {
963        if v == target {
964            n += 1
965        }
966    });
967    n
968}
969
970fn replace_reads(op: &mut PcodeOp, target: &Varnode, replacement: &Varnode) -> bool {
971    let mut found = false;
972    visit_reads_mut(op, &mut |v| {
973        if v == target {
974            *v = *replacement;
975            found = true;
976        }
977    });
978    found
979}
980
981pub fn visit_reads(op: &PcodeOp, f: &mut impl FnMut(&Varnode)) {
982    match op {
983        PcodeOp::Copy { input, .. } => f(input),
984        PcodeOp::Load { ptr, .. } => f(ptr),
985        PcodeOp::Store { ptr, val, .. } => {
986            f(ptr);
987            f(val);
988        }
989        PcodeOp::Branch { dest }
990        | PcodeOp::BranchInd { dest }
991        | PcodeOp::Call { dest }
992        | PcodeOp::CallInd { dest }
993        | PcodeOp::Return { dest } => f(dest),
994        PcodeOp::CBranch { dest, cond } => {
995            f(dest);
996            f(cond);
997        }
998        PcodeOp::Subpiece { input, .. } => f(input),
999        PcodeOp::IntNeg { input, .. }
1000        | PcodeOp::IntNot { input, .. }
1001        | PcodeOp::IntZext { input, .. }
1002        | PcodeOp::IntSext { input, .. }
1003        | PcodeOp::BoolNot { input, .. }
1004        | PcodeOp::FloatNeg { input, .. }
1005        | PcodeOp::FloatAbs { input, .. }
1006        | PcodeOp::FloatSqrt { input, .. }
1007        | PcodeOp::FloatNan { input, .. }
1008        | PcodeOp::Int2Float { input, .. }
1009        | PcodeOp::Float2Float { input, .. }
1010        | PcodeOp::Trunc { input, .. }
1011        | PcodeOp::FloatCeil { input, .. }
1012        | PcodeOp::FloatFloor { input, .. }
1013        | PcodeOp::FloatRound { input, .. }
1014        | PcodeOp::Popcount { input, .. }
1015        | PcodeOp::Lzcount { input, .. } => f(input),
1016        PcodeOp::IntAdd { left, right, .. }
1017        | PcodeOp::IntSub { left, right, .. }
1018        | PcodeOp::IntMult { left, right, .. }
1019        | PcodeOp::IntDiv { left, right, .. }
1020        | PcodeOp::IntSDiv { left, right, .. }
1021        | PcodeOp::IntRem { left, right, .. }
1022        | PcodeOp::IntSRem { left, right, .. }
1023        | PcodeOp::IntEq { left, right, .. }
1024        | PcodeOp::IntNotEq { left, right, .. }
1025        | PcodeOp::IntLess { left, right, .. }
1026        | PcodeOp::IntLessEq { left, right, .. }
1027        | PcodeOp::IntSLess { left, right, .. }
1028        | PcodeOp::IntSLessEq { left, right, .. }
1029        | PcodeOp::IntAnd { left, right, .. }
1030        | PcodeOp::IntOr { left, right, .. }
1031        | PcodeOp::IntXor { left, right, .. }
1032        | PcodeOp::IntLsl { left, right, .. }
1033        | PcodeOp::IntLsr { left, right, .. }
1034        | PcodeOp::IntAsr { left, right, .. }
1035        | PcodeOp::IntCarry { left, right, .. }
1036        | PcodeOp::IntSCarry { left, right, .. }
1037        | PcodeOp::IntSBorrow { left, right, .. }
1038        | PcodeOp::BoolAnd { left, right, .. }
1039        | PcodeOp::BoolOr { left, right, .. }
1040        | PcodeOp::BoolXor { left, right, .. }
1041        | PcodeOp::FloatAdd { left, right, .. }
1042        | PcodeOp::FloatSub { left, right, .. }
1043        | PcodeOp::FloatMult { left, right, .. }
1044        | PcodeOp::FloatDiv { left, right, .. }
1045        | PcodeOp::FloatEq { left, right, .. }
1046        | PcodeOp::FloatNotEq { left, right, .. }
1047        | PcodeOp::FloatLess { left, right, .. }
1048        | PcodeOp::FloatLessEq { left, right, .. } => {
1049            f(left);
1050            f(right);
1051        }
1052        PcodeOp::CallOther { inputs, .. } => {
1053            for v in inputs {
1054                f(v);
1055            }
1056        }
1057    }
1058}
1059
1060fn visit_reads_mut(op: &mut PcodeOp, f: &mut impl FnMut(&mut Varnode)) {
1061    match op {
1062        PcodeOp::Copy { input, .. } => f(input),
1063        PcodeOp::Load { ptr, .. } => f(ptr),
1064        PcodeOp::Store { ptr, val, .. } => {
1065            f(ptr);
1066            f(val);
1067        }
1068        PcodeOp::Branch { dest }
1069        | PcodeOp::BranchInd { dest }
1070        | PcodeOp::Call { dest }
1071        | PcodeOp::CallInd { dest }
1072        | PcodeOp::Return { dest } => f(dest),
1073        PcodeOp::CBranch { dest, cond } => {
1074            f(dest);
1075            f(cond);
1076        }
1077        PcodeOp::Subpiece { input, .. } => f(input),
1078        PcodeOp::IntNeg { input, .. }
1079        | PcodeOp::IntNot { input, .. }
1080        | PcodeOp::IntZext { input, .. }
1081        | PcodeOp::IntSext { input, .. }
1082        | PcodeOp::BoolNot { input, .. }
1083        | PcodeOp::FloatNeg { input, .. }
1084        | PcodeOp::FloatAbs { input, .. }
1085        | PcodeOp::FloatSqrt { input, .. }
1086        | PcodeOp::FloatNan { input, .. }
1087        | PcodeOp::Int2Float { input, .. }
1088        | PcodeOp::Float2Float { input, .. }
1089        | PcodeOp::Trunc { input, .. }
1090        | PcodeOp::FloatCeil { input, .. }
1091        | PcodeOp::FloatFloor { input, .. }
1092        | PcodeOp::FloatRound { input, .. }
1093        | PcodeOp::Popcount { input, .. }
1094        | PcodeOp::Lzcount { input, .. } => f(input),
1095        PcodeOp::IntAdd { left, right, .. }
1096        | PcodeOp::IntSub { left, right, .. }
1097        | PcodeOp::IntMult { left, right, .. }
1098        | PcodeOp::IntDiv { left, right, .. }
1099        | PcodeOp::IntSDiv { left, right, .. }
1100        | PcodeOp::IntRem { left, right, .. }
1101        | PcodeOp::IntSRem { left, right, .. }
1102        | PcodeOp::IntEq { left, right, .. }
1103        | PcodeOp::IntNotEq { left, right, .. }
1104        | PcodeOp::IntLess { left, right, .. }
1105        | PcodeOp::IntLessEq { left, right, .. }
1106        | PcodeOp::IntSLess { left, right, .. }
1107        | PcodeOp::IntSLessEq { left, right, .. }
1108        | PcodeOp::IntAnd { left, right, .. }
1109        | PcodeOp::IntOr { left, right, .. }
1110        | PcodeOp::IntXor { left, right, .. }
1111        | PcodeOp::IntLsl { left, right, .. }
1112        | PcodeOp::IntLsr { left, right, .. }
1113        | PcodeOp::IntAsr { left, right, .. }
1114        | PcodeOp::IntCarry { left, right, .. }
1115        | PcodeOp::IntSCarry { left, right, .. }
1116        | PcodeOp::IntSBorrow { left, right, .. }
1117        | PcodeOp::BoolAnd { left, right, .. }
1118        | PcodeOp::BoolOr { left, right, .. }
1119        | PcodeOp::BoolXor { left, right, .. }
1120        | PcodeOp::FloatAdd { left, right, .. }
1121        | PcodeOp::FloatSub { left, right, .. }
1122        | PcodeOp::FloatMult { left, right, .. }
1123        | PcodeOp::FloatDiv { left, right, .. }
1124        | PcodeOp::FloatEq { left, right, .. }
1125        | PcodeOp::FloatNotEq { left, right, .. }
1126        | PcodeOp::FloatLess { left, right, .. }
1127        | PcodeOp::FloatLessEq { left, right, .. } => {
1128            f(left);
1129            f(right);
1130        }
1131        PcodeOp::CallOther { inputs, .. } => {
1132            for v in inputs {
1133                f(v);
1134            }
1135        }
1136    }
1137}
1138
1139/// A single P-code operation.
1140///
1141/// Variant naming follows Ghidra's P-code reference.
1142/// See: <https://ghidra.re/courses/languages/html/pcoderef.html>
1143#[derive(Debug, Clone, PartialEq, Eq)]
1144pub enum PcodeOp {
1145    // ── Data Movement ──────────────────────────────────────────────
1146    Copy {
1147        out: Varnode,
1148        input: Varnode,
1149    },
1150    Load {
1151        out: Varnode,
1152        space: AddressSpaceId,
1153        ptr: Varnode,
1154    },
1155    Store {
1156        space: AddressSpaceId,
1157        ptr: Varnode,
1158        val: Varnode,
1159    },
1160
1161    // ── Branching ──────────────────────────────────────────────────
1162    Branch {
1163        dest: Varnode,
1164    },
1165    CBranch {
1166        dest: Varnode,
1167        cond: Varnode,
1168    },
1169    BranchInd {
1170        dest: Varnode,
1171    },
1172    Call {
1173        dest: Varnode,
1174    },
1175    CallInd {
1176        dest: Varnode,
1177    },
1178    Return {
1179        dest: Varnode,
1180    },
1181
1182    // ── Integer Arithmetic ─────────────────────────────────────────
1183    IntAdd {
1184        out: Varnode,
1185        left: Varnode,
1186        right: Varnode,
1187    },
1188    IntSub {
1189        out: Varnode,
1190        left: Varnode,
1191        right: Varnode,
1192    },
1193    IntMult {
1194        out: Varnode,
1195        left: Varnode,
1196        right: Varnode,
1197    },
1198    IntDiv {
1199        out: Varnode,
1200        left: Varnode,
1201        right: Varnode,
1202    },
1203    IntSDiv {
1204        out: Varnode,
1205        left: Varnode,
1206        right: Varnode,
1207    },
1208    IntRem {
1209        out: Varnode,
1210        left: Varnode,
1211        right: Varnode,
1212    },
1213    IntSRem {
1214        out: Varnode,
1215        left: Varnode,
1216        right: Varnode,
1217    },
1218    IntNeg {
1219        out: Varnode,
1220        input: Varnode,
1221    },
1222
1223    // ── Integer Comparison ─────────────────────────────────────────
1224    IntEq {
1225        out: Varnode,
1226        left: Varnode,
1227        right: Varnode,
1228    },
1229    IntNotEq {
1230        out: Varnode,
1231        left: Varnode,
1232        right: Varnode,
1233    },
1234    IntLess {
1235        out: Varnode,
1236        left: Varnode,
1237        right: Varnode,
1238    },
1239    IntLessEq {
1240        out: Varnode,
1241        left: Varnode,
1242        right: Varnode,
1243    },
1244    IntSLess {
1245        out: Varnode,
1246        left: Varnode,
1247        right: Varnode,
1248    },
1249    IntSLessEq {
1250        out: Varnode,
1251        left: Varnode,
1252        right: Varnode,
1253    },
1254
1255    // ── Integer Logical / Bitwise ──────────────────────────────────
1256    IntAnd {
1257        out: Varnode,
1258        left: Varnode,
1259        right: Varnode,
1260    },
1261    IntOr {
1262        out: Varnode,
1263        left: Varnode,
1264        right: Varnode,
1265    },
1266    IntXor {
1267        out: Varnode,
1268        left: Varnode,
1269        right: Varnode,
1270    },
1271    IntNot {
1272        out: Varnode,
1273        input: Varnode,
1274    },
1275
1276    // ── Shift ──────────────────────────────────────────────────────
1277    IntLsl {
1278        out: Varnode,
1279        left: Varnode,
1280        right: Varnode,
1281    },
1282    IntLsr {
1283        out: Varnode,
1284        left: Varnode,
1285        right: Varnode,
1286    },
1287    IntAsr {
1288        out: Varnode,
1289        left: Varnode,
1290        right: Varnode,
1291    },
1292
1293    // ── Extension / Truncation ─────────────────────────────────────
1294    IntZext {
1295        out: Varnode,
1296        input: Varnode,
1297    },
1298    IntSext {
1299        out: Varnode,
1300        input: Varnode,
1301    },
1302    Subpiece {
1303        out: Varnode,
1304        input: Varnode,
1305        lsb: u32,
1306    },
1307
1308    // ── Carry / Borrow ─────────────────────────────────────────────
1309    IntCarry {
1310        out: Varnode,
1311        left: Varnode,
1312        right: Varnode,
1313    },
1314    IntSCarry {
1315        out: Varnode,
1316        left: Varnode,
1317        right: Varnode,
1318    },
1319    IntSBorrow {
1320        out: Varnode,
1321        left: Varnode,
1322        right: Varnode,
1323    },
1324
1325    // ── Boolean ────────────────────────────────────────────────────
1326    BoolAnd {
1327        out: Varnode,
1328        left: Varnode,
1329        right: Varnode,
1330    },
1331    BoolOr {
1332        out: Varnode,
1333        left: Varnode,
1334        right: Varnode,
1335    },
1336    BoolXor {
1337        out: Varnode,
1338        left: Varnode,
1339        right: Varnode,
1340    },
1341    BoolNot {
1342        out: Varnode,
1343        input: Varnode,
1344    },
1345
1346    // ── Floating Point Arithmetic ──────────────────────────────────
1347    FloatAdd {
1348        out: Varnode,
1349        left: Varnode,
1350        right: Varnode,
1351    },
1352    FloatSub {
1353        out: Varnode,
1354        left: Varnode,
1355        right: Varnode,
1356    },
1357    FloatMult {
1358        out: Varnode,
1359        left: Varnode,
1360        right: Varnode,
1361    },
1362    FloatDiv {
1363        out: Varnode,
1364        left: Varnode,
1365        right: Varnode,
1366    },
1367    FloatNeg {
1368        out: Varnode,
1369        input: Varnode,
1370    },
1371    FloatAbs {
1372        out: Varnode,
1373        input: Varnode,
1374    },
1375    FloatSqrt {
1376        out: Varnode,
1377        input: Varnode,
1378    },
1379
1380    // ── Floating Point Comparison ──────────────────────────────────
1381    FloatEq {
1382        out: Varnode,
1383        left: Varnode,
1384        right: Varnode,
1385    },
1386    FloatNotEq {
1387        out: Varnode,
1388        left: Varnode,
1389        right: Varnode,
1390    },
1391    FloatLess {
1392        out: Varnode,
1393        left: Varnode,
1394        right: Varnode,
1395    },
1396    FloatLessEq {
1397        out: Varnode,
1398        left: Varnode,
1399        right: Varnode,
1400    },
1401    FloatNan {
1402        out: Varnode,
1403        input: Varnode,
1404    },
1405
1406    // ── Floating Point Conversion ──────────────────────────────────
1407    Int2Float {
1408        out: Varnode,
1409        input: Varnode,
1410    },
1411    Float2Float {
1412        out: Varnode,
1413        input: Varnode,
1414    },
1415    Trunc {
1416        out: Varnode,
1417        input: Varnode,
1418    },
1419    FloatCeil {
1420        out: Varnode,
1421        input: Varnode,
1422    },
1423    FloatFloor {
1424        out: Varnode,
1425        input: Varnode,
1426    },
1427    FloatRound {
1428        out: Varnode,
1429        input: Varnode,
1430    },
1431
1432    // ── Bit Manipulation ───────────────────────────────────────────
1433    Popcount {
1434        out: Varnode,
1435        input: Varnode,
1436    },
1437    Lzcount {
1438        out: Varnode,
1439        input: Varnode,
1440    },
1441
1442    // ── Miscellaneous ──────────────────────────────────────────────
1443    /// User-defined or architecture-specific operation.
1444    CallOther {
1445        out: Option<Varnode>,
1446        func_id: u64,
1447        inputs: Vec<Varnode>,
1448    },
1449}
1450
1451#[cfg(test)]
1452mod tests {
1453    use super::*;
1454
1455    #[test]
1456    fn optimize_intand_all_ones_wide_size_no_panic() {
1457        let mut ops = vec![
1458            PcodeOp::IntAnd {
1459                out: Varnode::unique(0, 16),
1460                left: Varnode::register(0, 16),
1461                right: Varnode::constant(u64::MAX, 16),
1462            },
1463            PcodeOp::Copy {
1464                out: Varnode::register(16, 16),
1465                input: Varnode::unique(0, 16),
1466            },
1467        ];
1468
1469        optimize(&mut ops);
1470
1471        assert!(matches!(
1472            ops.as_slice(),
1473            [PcodeOp::Copy { out, input }]
1474                if *out == Varnode::register(16, 16)
1475                    && *input == Varnode::register(0, 16)
1476        ));
1477    }
1478
1479    #[test]
1480    fn optimize_folds_constant_integer_ops() {
1481        let mut ops = vec![
1482            PcodeOp::IntAdd {
1483                out: Varnode::unique(0, 4),
1484                left: Varnode::constant(0xffff_ffff, 4),
1485                right: Varnode::constant(2, 4),
1486            },
1487            PcodeOp::IntXor {
1488                out: Varnode::unique(8, 4),
1489                left: Varnode::unique(0, 4),
1490                right: Varnode::constant(0x10, 4),
1491            },
1492            PcodeOp::Copy {
1493                out: Varnode::register(0, 4),
1494                input: Varnode::unique(8, 4),
1495            },
1496        ];
1497
1498        optimize(&mut ops);
1499
1500        assert_eq!(
1501            ops,
1502            vec![PcodeOp::Copy {
1503                out: Varnode::register(0, 4),
1504                input: Varnode::constant(0x11, 4),
1505            }]
1506        );
1507    }
1508
1509    #[test]
1510    fn optimize_folds_constant_comparisons_and_bool_ops() {
1511        let mut ops = vec![
1512            PcodeOp::IntSLess {
1513                out: Varnode::unique(0, 1),
1514                left: Varnode::constant(0xff, 1),
1515                right: Varnode::constant(1, 1),
1516            },
1517            PcodeOp::BoolNot {
1518                out: Varnode::unique(1, 1),
1519                input: Varnode::unique(0, 1),
1520            },
1521            PcodeOp::Copy {
1522                out: Varnode::register(0, 1),
1523                input: Varnode::unique(1, 1),
1524            },
1525        ];
1526
1527        optimize(&mut ops);
1528
1529        assert_eq!(
1530            ops,
1531            vec![PcodeOp::Copy {
1532                out: Varnode::register(0, 1),
1533                input: Varnode::constant(0, 1),
1534            }]
1535        );
1536    }
1537
1538    #[test]
1539    fn optimize_folds_constant_shifts() {
1540        let mut ops = vec![
1541            PcodeOp::IntAsr {
1542                out: Varnode::unique(0, 1),
1543                left: Varnode::constant(0x80, 1),
1544                right: Varnode::constant(4, 1),
1545            },
1546            PcodeOp::Copy {
1547                out: Varnode::register(0, 1),
1548                input: Varnode::unique(0, 1),
1549            },
1550        ];
1551
1552        optimize(&mut ops);
1553
1554        assert_eq!(
1555            ops,
1556            vec![PcodeOp::Copy {
1557                out: Varnode::register(0, 1),
1558                input: Varnode::constant(0xf8, 1),
1559            }]
1560        );
1561    }
1562
1563    #[test]
1564    fn optimize_sign_extend_constant_masks_to_output_size() {
1565        let mut ops = vec![
1566            PcodeOp::IntSext {
1567                out: Varnode::unique(0, 2),
1568                input: Varnode::constant(0x80, 1),
1569            },
1570            PcodeOp::Copy {
1571                out: Varnode::register(0, 2),
1572                input: Varnode::unique(0, 2),
1573            },
1574        ];
1575
1576        optimize(&mut ops);
1577
1578        assert_eq!(
1579            ops,
1580            vec![PcodeOp::Copy {
1581                out: Varnode::register(0, 2),
1582                input: Varnode::constant(0xff80, 2),
1583            }]
1584        );
1585    }
1586}