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/// A decoded and lifted instruction.
86#[derive(Debug, Clone)]
87pub struct Instruction {
88    /// Number of bytes consumed by this instruction.
89    pub len: u64,
90    /// Human-readable disassembly (e.g. "MOV RAX,RBX").
91    pub disassembly: String,
92    /// P-code operations (peephole-optimized).
93    pub ops: Vec<PcodeOp>,
94}
95
96/// Decode error returned when an instruction cannot be parsed.
97#[derive(Debug, Clone)]
98pub enum DecodeError {
99    /// The byte sequence does not match any known instruction encoding.
100    UnknownInstruction,
101}
102
103impl core::fmt::Display for DecodeError {
104    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105        match self {
106            DecodeError::UnknownInstruction => write!(f, "unknown instruction encoding"),
107        }
108    }
109}
110
111/// Offset all Unique-space varnode offsets in an op by the given amount.
112/// Used to avoid unique offset collisions when combining P-code from multiple subtables.
113pub fn offset_unique_varnodes(op: &mut PcodeOp, offset: u64) {
114    // Offset outputs
115    if let Some(out) = get_output_mut(op) {
116        if out.space == AddressSpaceId::Unique {
117            out.offset += offset;
118        }
119    }
120    // Offset inputs
121    visit_reads_mut(op, &mut |v| {
122        if v.space == AddressSpaceId::Unique {
123            v.offset += offset;
124        }
125    });
126}
127
128/// Peephole-optimize a P-code op sequence:
129/// - Remove identity `Subpiece { lsb: 0 }` where input.size == out.size
130/// - Forward-substitute `Copy` chains (A=B, C=A → C=B) when the
131///   intermediate is a unique varnode used only once after its definition
132pub fn optimize(ops: &mut Vec<PcodeOp>) {
133    // Run passes until fixpoint (later passes create opportunities for earlier ones)
134    for _round in 0..4 {
135        let before = ops.len();
136        optimize_once(ops);
137        if ops.len() == before {
138            break;
139        }
140    }
141}
142
143fn optimize_once(ops: &mut Vec<PcodeOp>) {
144    // Pass 0: constant folding — IntZext/IntSext of constants
145    for op in ops.iter_mut() {
146        match op {
147            PcodeOp::IntZext { out, input } if input.space == AddressSpaceId::Const => {
148                *op = PcodeOp::Copy {
149                    out: *out,
150                    input: Varnode::constant(input.offset, out.size),
151                };
152            }
153            PcodeOp::IntSext { out, input } if input.space == AddressSpaceId::Const => {
154                // Sign-extend: if the high bit of input is set, fill upper bits
155                let val = input.offset;
156                let in_bits = (input.size as u64) * 8;
157                let extended = if in_bits < 64 && (val >> (in_bits - 1)) & 1 != 0 {
158                    val | (!0u64 << in_bits)
159                } else {
160                    val
161                };
162                *op = PcodeOp::Copy {
163                    out: *out,
164                    input: Varnode::constant(extended, out.size),
165                };
166            }
167            // Shift by zero → Copy
168            PcodeOp::IntLsr { out, left, right }
169            | PcodeOp::IntLsl { out, left, right }
170            | PcodeOp::IntAsr { out, left, right }
171                if right.space == AddressSpaceId::Const && right.offset == 0 =>
172            {
173                *op = PcodeOp::Copy {
174                    out: *out,
175                    input: *left,
176                };
177            }
178            // OR with zero → Copy
179            PcodeOp::IntOr { out, left, right }
180                if right.space == AddressSpaceId::Const && right.offset == 0 =>
181            {
182                *op = PcodeOp::Copy {
183                    out: *out,
184                    input: *left,
185                };
186            }
187            PcodeOp::IntOr { out, left, right }
188                if left.space == AddressSpaceId::Const && left.offset == 0 =>
189            {
190                *op = PcodeOp::Copy {
191                    out: *out,
192                    input: *right,
193                };
194            }
195            // AND with all-ones (for the size) → Copy
196            PcodeOp::IntAnd { out, left, right }
197                if right.space == AddressSpaceId::Const
198                    && right.offset == u64::MAX >> (64 - out.size as u64 * 8) =>
199            {
200                *op = PcodeOp::Copy {
201                    out: *out,
202                    input: *left,
203                };
204            }
205            _ => {}
206        }
207    }
208
209    // Pass 1: eliminate identity Subpiece
210    for op in ops.iter_mut() {
211        if let PcodeOp::Subpiece { out, input, lsb: 0 } = op {
212            if out.size == input.size {
213                *op = PcodeOp::Copy {
214                    out: *out,
215                    input: *input,
216                };
217            }
218        }
219    }
220
221    // Compute unique-varnode analysis once. Passes below share and recompute only
222    // after structural mutations (removals). Indices in NextAccess::Read are
223    // invalidated by any removal, so we must recompute after each ops.remove().
224    let mut analysis = analyze_unique_outputs(ops);
225
226    // Pass 1a: redundant IntAnd — if IntAnd{out1, x, mask} followed by IntAnd{out2, out1, mask}
227    // with same mask and out1 used only once, remove the second
228    let mut i = 0;
229    while i + 1 < ops.len() {
230        let collapse = if let PcodeOp::IntAnd {
231            out: out1,
232            left: _,
233            right: mask1,
234        } = &ops[i]
235        {
236            if out1.space == AddressSpaceId::Unique && mask1.space == AddressSpaceId::Const {
237                if let PcodeOp::IntAnd {
238                    out: out2,
239                    left: in2,
240                    right: mask2,
241                } = &ops[i + 1]
242                {
243                    if *in2 == *out1 && *mask2 == *mask1 {
244                        // out1 is Unique (checked above), so analysis[i] is always Some.
245                        // Use pattern match to be explicit rather than unwrap_or which
246                        // would silently treat non-Unique outputs as zero reads.
247                        let total_reads = match analysis.get(i) {
248                            Some(Some(info)) => info.future_reads,
249                            _ => usize::MAX, // conservative: don't collapse if unknown
250                        };
251                        if total_reads == 1 {
252                            Some(*out2)
253                        } else {
254                            None
255                        }
256                    } else {
257                        None
258                    }
259                } else {
260                    None
261                }
262            } else {
263                None
264            }
265        } else {
266            None
267        };
268
269        if let Some(new_out) = collapse {
270            if let PcodeOp::IntAnd { out, .. } = &mut ops[i] {
271                *out = new_out;
272            }
273            ops.remove(i + 1);
274            analysis = analyze_unique_outputs(ops);
275            continue;
276        }
277        i += 1;
278    }
279
280    // Pass 2: forward-substitute single-use Copy chains.
281    // If ops[i] is Copy { out: A, input: B } and A is Unique,
282    // and exactly one later op reads A, replace that read with B.
283    // Analysis is still valid from pass 1a (no in-place mutations between).
284    let mut i = 0;
285    while i < ops.len() {
286        if let PcodeOp::Copy { out, input } = &ops[i] {
287            if out.space == AddressSpaceId::Unique {
288                let target = *out;
289                let replacement = *input;
290                let entry = analysis.get(i).and_then(|entry| entry.as_ref());
291                if let Some(UniqueOutputInfo {
292                    future_reads: 1,
293                    next_access: Some(NextAccess::Read(read_idx)),
294                }) = entry
295                {
296                    replace_reads(&mut ops[*read_idx], &target, &replacement);
297                    ops.remove(i);
298                    analysis = analyze_unique_outputs(ops);
299                    continue;
300                }
301            }
302        }
303        i += 1;
304    }
305
306    // Pass 2b + 3: overwrite elimination and dead code elimination.
307    // Remove ops that write to Unique varnodes that are either overwritten before
308    // any read (2b) or never read at all (3). These removals are independent —
309    // removing a dead/overwritten op cannot make another op live — so we collect
310    // all indices in one scan and batch-remove in reverse order.
311    // Analysis is still valid from pass 2 (no in-place mutations between).
312    {
313        let mut to_remove = Vec::new();
314        for (i, entry) in analysis.iter().enumerate() {
315            if let Some(info) = entry {
316                if info.future_reads == 0
317                    || matches!(info.next_access, Some(NextAccess::Write))
318                {
319                    to_remove.push(i);
320                }
321            }
322        }
323        for &idx in to_remove.iter().rev() {
324            ops.remove(idx);
325        }
326    }
327
328    // Pass 4: sink unique outputs into subsequent Copy destinations.
329    // If ops[i] writes to Unique(X) and some later ops[j] is Copy { out: dest, input: Unique(X) },
330    // and Unique(X) is read exactly once (by that Copy), and dest is not written or read between
331    // i and j, rewrite ops[i] to output directly to dest and remove the Copy.
332    // Must recompute — batch removal above invalidated indices.
333    analysis = analyze_unique_outputs(ops);
334    let mut i = 0;
335    while i < ops.len() {
336        let should_sink = match (
337            get_output(&ops[i]),
338            analysis.get(i).and_then(|entry| entry.as_ref()),
339        ) {
340            (
341                Some(out),
342                Some(UniqueOutputInfo {
343                    future_reads: 1,
344                    next_access: Some(NextAccess::Read(copy_idx)),
345                }),
346            ) if out.space == AddressSpaceId::Unique => {
347                if let PcodeOp::Copy {
348                    out: copy_dest,
349                    input: copy_src,
350                } = &ops[*copy_idx]
351                {
352                    if *copy_src == out {
353                        let d = *copy_dest;
354                        let safe = (i + 1..*copy_idx)
355                            .all(|k| count_reads(&ops[k], &d) == 0 && !writes_to(&ops[k], &d));
356                        if safe {
357                            Some((*copy_idx, d))
358                        } else {
359                            None
360                        }
361                    } else {
362                        None
363                    }
364                } else {
365                    None
366                }
367            }
368            _ => None,
369        };
370
371        if let Some((copy_idx, new_dest)) = should_sink {
372            if let Some(out) = get_output_mut(&mut ops[i]) {
373                *out = new_dest;
374            }
375            ops.remove(copy_idx);
376            analysis = analyze_unique_outputs(ops);
377            continue;
378        }
379        i += 1;
380    }
381}
382
383#[derive(Clone, Copy)]
384struct UniqueOutputInfo {
385    future_reads: usize,
386    next_access: Option<NextAccess>,
387}
388
389#[derive(Clone, Copy)]
390enum NextAccess {
391    Read(usize),
392    Write,
393}
394
395fn analyze_unique_outputs(ops: &[PcodeOp]) -> Vec<Option<UniqueOutputInfo>> {
396    let mut future_reads = BTreeMap::<Varnode, usize>::new();
397    let mut next_access = BTreeMap::<Varnode, NextAccess>::new();
398    let mut result = vec![None; ops.len()];
399
400    // Pre-scan: find Unique varnodes that are written on both sides of an
401    // intra-instruction CBranch (Const-space dest). These are conditional-select
402    // patterns (AArch64 CSEL/CSINC/CNEG) where both writes are live because
403    // the branch may skip the second write. For these varnodes, the backward
404    // analysis must not clear future_reads or mark next_access as Write.
405    let mut cbranch_protected = BTreeSet::<Varnode>::new();
406    {
407        // Find CBranch indices
408        let cbranch_indices: Vec<usize> = ops.iter().enumerate()
409            .filter(|(_, op)| matches!(op, PcodeOp::CBranch { dest, .. } if dest.space == AddressSpaceId::Const))
410            .map(|(i, _)| i)
411            .collect();
412        for &cb_idx in &cbranch_indices {
413            // Collect Unique varnodes written before and after this CBranch
414            let mut before = BTreeSet::new();
415            let mut after = BTreeSet::new();
416            for (i, op) in ops.iter().enumerate() {
417                if let Some(out) = get_output(op) {
418                    if out.space == AddressSpaceId::Unique {
419                        if i < cb_idx {
420                            before.insert(out);
421                        } else if i > cb_idx {
422                            after.insert(out);
423                        }
424                    }
425                }
426            }
427            for v in before.intersection(&after) {
428                cbranch_protected.insert(*v);
429            }
430        }
431    }
432
433    for (idx, op) in ops.iter().enumerate().rev() {
434        if let Some(out) = get_output(op).filter(|out| out.space == AddressSpaceId::Unique) {
435            if cbranch_protected.contains(&out) {
436                // This varnode is written on both sides of a CBranch — both writes
437                // are potentially live. Return None so no pass touches either write.
438                result[idx] = None;
439            } else {
440                result[idx] = Some(UniqueOutputInfo {
441                    future_reads: future_reads.get(&out).copied().unwrap_or(0),
442                    next_access: next_access.get(&out).copied(),
443                });
444                future_reads.remove(&out);
445                next_access.insert(out, NextAccess::Write);
446            }
447        }
448
449        visit_reads(op, &mut |v| {
450            if v.space == AddressSpaceId::Unique {
451                *future_reads.entry(*v).or_insert(0) += 1;
452                next_access.insert(*v, NextAccess::Read(idx));
453            }
454        });
455    }
456
457    result
458}
459
460pub fn get_output(op: &PcodeOp) -> Option<Varnode> {
461    match op {
462        PcodeOp::Copy { out, .. }
463        | PcodeOp::Load { out, .. }
464        | PcodeOp::Subpiece { out, .. }
465        | PcodeOp::IntNeg { out, .. }
466        | PcodeOp::IntNot { out, .. }
467        | PcodeOp::IntZext { out, .. }
468        | PcodeOp::IntSext { out, .. }
469        | PcodeOp::BoolNot { out, .. }
470        | PcodeOp::FloatNeg { out, .. }
471        | PcodeOp::FloatAbs { out, .. }
472        | PcodeOp::FloatSqrt { out, .. }
473        | PcodeOp::FloatNan { out, .. }
474        | PcodeOp::Int2Float { out, .. }
475        | PcodeOp::Float2Float { out, .. }
476        | PcodeOp::Trunc { out, .. }
477        | PcodeOp::FloatCeil { out, .. }
478        | PcodeOp::FloatFloor { out, .. }
479        | PcodeOp::FloatRound { out, .. }
480        | PcodeOp::Popcount { out, .. }
481        | PcodeOp::Lzcount { out, .. }
482        | PcodeOp::IntAdd { out, .. }
483        | PcodeOp::IntSub { out, .. }
484        | PcodeOp::IntMult { out, .. }
485        | PcodeOp::IntDiv { out, .. }
486        | PcodeOp::IntSDiv { out, .. }
487        | PcodeOp::IntRem { out, .. }
488        | PcodeOp::IntSRem { out, .. }
489        | PcodeOp::IntEq { out, .. }
490        | PcodeOp::IntNotEq { out, .. }
491        | PcodeOp::IntLess { out, .. }
492        | PcodeOp::IntLessEq { out, .. }
493        | PcodeOp::IntSLess { out, .. }
494        | PcodeOp::IntSLessEq { out, .. }
495        | PcodeOp::IntAnd { out, .. }
496        | PcodeOp::IntOr { out, .. }
497        | PcodeOp::IntXor { out, .. }
498        | PcodeOp::IntLsl { out, .. }
499        | PcodeOp::IntLsr { out, .. }
500        | PcodeOp::IntAsr { out, .. }
501        | PcodeOp::IntCarry { out, .. }
502        | PcodeOp::IntSCarry { out, .. }
503        | PcodeOp::IntSBorrow { out, .. }
504        | PcodeOp::BoolAnd { out, .. }
505        | PcodeOp::BoolOr { out, .. }
506        | PcodeOp::BoolXor { out, .. }
507        | PcodeOp::FloatAdd { out, .. }
508        | PcodeOp::FloatSub { out, .. }
509        | PcodeOp::FloatMult { out, .. }
510        | PcodeOp::FloatDiv { out, .. }
511        | PcodeOp::FloatEq { out, .. }
512        | PcodeOp::FloatNotEq { out, .. }
513        | PcodeOp::FloatLess { out, .. }
514        | PcodeOp::FloatLessEq { out, .. } => Some(*out),
515        _ => None,
516    }
517}
518
519fn get_output_mut(op: &mut PcodeOp) -> Option<&mut Varnode> {
520    match op {
521        PcodeOp::Copy { out, .. }
522        | PcodeOp::Load { out, .. }
523        | PcodeOp::Subpiece { out, .. }
524        | PcodeOp::IntNeg { out, .. }
525        | PcodeOp::IntNot { out, .. }
526        | PcodeOp::IntZext { out, .. }
527        | PcodeOp::IntSext { out, .. }
528        | PcodeOp::BoolNot { out, .. }
529        | PcodeOp::FloatNeg { out, .. }
530        | PcodeOp::FloatAbs { out, .. }
531        | PcodeOp::FloatSqrt { out, .. }
532        | PcodeOp::FloatNan { out, .. }
533        | PcodeOp::Int2Float { out, .. }
534        | PcodeOp::Float2Float { out, .. }
535        | PcodeOp::Trunc { out, .. }
536        | PcodeOp::FloatCeil { out, .. }
537        | PcodeOp::FloatFloor { out, .. }
538        | PcodeOp::FloatRound { out, .. }
539        | PcodeOp::Popcount { out, .. }
540        | PcodeOp::Lzcount { out, .. }
541        | PcodeOp::IntAdd { out, .. }
542        | PcodeOp::IntSub { out, .. }
543        | PcodeOp::IntMult { out, .. }
544        | PcodeOp::IntDiv { out, .. }
545        | PcodeOp::IntSDiv { out, .. }
546        | PcodeOp::IntRem { out, .. }
547        | PcodeOp::IntSRem { out, .. }
548        | PcodeOp::IntEq { out, .. }
549        | PcodeOp::IntNotEq { out, .. }
550        | PcodeOp::IntLess { out, .. }
551        | PcodeOp::IntLessEq { out, .. }
552        | PcodeOp::IntSLess { out, .. }
553        | PcodeOp::IntSLessEq { out, .. }
554        | PcodeOp::IntAnd { out, .. }
555        | PcodeOp::IntOr { out, .. }
556        | PcodeOp::IntXor { out, .. }
557        | PcodeOp::IntLsl { out, .. }
558        | PcodeOp::IntLsr { out, .. }
559        | PcodeOp::IntAsr { out, .. }
560        | PcodeOp::IntCarry { out, .. }
561        | PcodeOp::IntSCarry { out, .. }
562        | PcodeOp::IntSBorrow { out, .. }
563        | PcodeOp::BoolAnd { out, .. }
564        | PcodeOp::BoolOr { out, .. }
565        | PcodeOp::BoolXor { out, .. }
566        | PcodeOp::FloatAdd { out, .. }
567        | PcodeOp::FloatSub { out, .. }
568        | PcodeOp::FloatMult { out, .. }
569        | PcodeOp::FloatDiv { out, .. }
570        | PcodeOp::FloatEq { out, .. }
571        | PcodeOp::FloatNotEq { out, .. }
572        | PcodeOp::FloatLess { out, .. }
573        | PcodeOp::FloatLessEq { out, .. } => Some(out),
574        _ => None,
575    }
576}
577
578pub fn writes_to(op: &PcodeOp, target: &Varnode) -> bool {
579    match op {
580        PcodeOp::Copy { out, .. }
581        | PcodeOp::Load { out, .. }
582        | PcodeOp::Subpiece { out, .. }
583        | PcodeOp::IntNeg { out, .. }
584        | PcodeOp::IntNot { out, .. }
585        | PcodeOp::IntZext { out, .. }
586        | PcodeOp::IntSext { out, .. }
587        | PcodeOp::BoolNot { out, .. }
588        | PcodeOp::FloatNeg { out, .. }
589        | PcodeOp::FloatAbs { out, .. }
590        | PcodeOp::FloatSqrt { out, .. }
591        | PcodeOp::FloatNan { out, .. }
592        | PcodeOp::Int2Float { out, .. }
593        | PcodeOp::Float2Float { out, .. }
594        | PcodeOp::Trunc { out, .. }
595        | PcodeOp::FloatCeil { out, .. }
596        | PcodeOp::FloatFloor { out, .. }
597        | PcodeOp::FloatRound { out, .. }
598        | PcodeOp::Popcount { out, .. }
599        | PcodeOp::Lzcount { out, .. }
600        | PcodeOp::IntAdd { out, .. }
601        | PcodeOp::IntSub { out, .. }
602        | PcodeOp::IntMult { out, .. }
603        | PcodeOp::IntDiv { out, .. }
604        | PcodeOp::IntSDiv { out, .. }
605        | PcodeOp::IntRem { out, .. }
606        | PcodeOp::IntSRem { out, .. }
607        | PcodeOp::IntEq { out, .. }
608        | PcodeOp::IntNotEq { out, .. }
609        | PcodeOp::IntLess { out, .. }
610        | PcodeOp::IntLessEq { out, .. }
611        | PcodeOp::IntSLess { out, .. }
612        | PcodeOp::IntSLessEq { out, .. }
613        | PcodeOp::IntAnd { out, .. }
614        | PcodeOp::IntOr { out, .. }
615        | PcodeOp::IntXor { out, .. }
616        | PcodeOp::IntLsl { out, .. }
617        | PcodeOp::IntLsr { out, .. }
618        | PcodeOp::IntAsr { out, .. }
619        | PcodeOp::IntCarry { out, .. }
620        | PcodeOp::IntSCarry { out, .. }
621        | PcodeOp::IntSBorrow { out, .. }
622        | PcodeOp::BoolAnd { out, .. }
623        | PcodeOp::BoolOr { out, .. }
624        | PcodeOp::BoolXor { out, .. }
625        | PcodeOp::FloatAdd { out, .. }
626        | PcodeOp::FloatSub { out, .. }
627        | PcodeOp::FloatMult { out, .. }
628        | PcodeOp::FloatDiv { out, .. }
629        | PcodeOp::FloatEq { out, .. }
630        | PcodeOp::FloatNotEq { out, .. }
631        | PcodeOp::FloatLess { out, .. }
632        | PcodeOp::FloatLessEq { out, .. } => out == target,
633        PcodeOp::CallOther { out: Some(out), .. } => out == target,
634        _ => false,
635    }
636}
637
638/// Check if a P-code op reads from a specific varnode.
639pub fn reads_varnode(op: &PcodeOp, target: &Varnode) -> bool {
640    count_reads(op, target) > 0
641}
642
643pub fn count_reads(op: &PcodeOp, target: &Varnode) -> usize {
644    let mut n = 0;
645    visit_reads(op, &mut |v| {
646        if v == target {
647            n += 1
648        }
649    });
650    n
651}
652
653fn replace_reads(op: &mut PcodeOp, target: &Varnode, replacement: &Varnode) -> bool {
654    let mut found = false;
655    visit_reads_mut(op, &mut |v| {
656        if v == target {
657            *v = *replacement;
658            found = true;
659        }
660    });
661    found
662}
663
664pub fn visit_reads(op: &PcodeOp, f: &mut impl FnMut(&Varnode)) {
665    match op {
666        PcodeOp::Copy { input, .. } => f(input),
667        PcodeOp::Load { ptr, .. } => f(ptr),
668        PcodeOp::Store { ptr, val, .. } => {
669            f(ptr);
670            f(val);
671        }
672        PcodeOp::Branch { dest }
673        | PcodeOp::BranchInd { dest }
674        | PcodeOp::Call { dest }
675        | PcodeOp::CallInd { dest }
676        | PcodeOp::Return { dest } => f(dest),
677        PcodeOp::CBranch { dest, cond } => {
678            f(dest);
679            f(cond);
680        }
681        PcodeOp::Subpiece { input, .. } => f(input),
682        PcodeOp::IntNeg { input, .. }
683        | PcodeOp::IntNot { input, .. }
684        | PcodeOp::IntZext { input, .. }
685        | PcodeOp::IntSext { input, .. }
686        | PcodeOp::BoolNot { input, .. }
687        | PcodeOp::FloatNeg { input, .. }
688        | PcodeOp::FloatAbs { input, .. }
689        | PcodeOp::FloatSqrt { input, .. }
690        | PcodeOp::FloatNan { input, .. }
691        | PcodeOp::Int2Float { input, .. }
692        | PcodeOp::Float2Float { input, .. }
693        | PcodeOp::Trunc { input, .. }
694        | PcodeOp::FloatCeil { input, .. }
695        | PcodeOp::FloatFloor { input, .. }
696        | PcodeOp::FloatRound { input, .. }
697        | PcodeOp::Popcount { input, .. }
698        | PcodeOp::Lzcount { input, .. } => f(input),
699        PcodeOp::IntAdd { left, right, .. }
700        | PcodeOp::IntSub { left, right, .. }
701        | PcodeOp::IntMult { left, right, .. }
702        | PcodeOp::IntDiv { left, right, .. }
703        | PcodeOp::IntSDiv { left, right, .. }
704        | PcodeOp::IntRem { left, right, .. }
705        | PcodeOp::IntSRem { left, right, .. }
706        | PcodeOp::IntEq { left, right, .. }
707        | PcodeOp::IntNotEq { left, right, .. }
708        | PcodeOp::IntLess { left, right, .. }
709        | PcodeOp::IntLessEq { left, right, .. }
710        | PcodeOp::IntSLess { left, right, .. }
711        | PcodeOp::IntSLessEq { left, right, .. }
712        | PcodeOp::IntAnd { left, right, .. }
713        | PcodeOp::IntOr { left, right, .. }
714        | PcodeOp::IntXor { left, right, .. }
715        | PcodeOp::IntLsl { left, right, .. }
716        | PcodeOp::IntLsr { left, right, .. }
717        | PcodeOp::IntAsr { left, right, .. }
718        | PcodeOp::IntCarry { left, right, .. }
719        | PcodeOp::IntSCarry { left, right, .. }
720        | PcodeOp::IntSBorrow { left, right, .. }
721        | PcodeOp::BoolAnd { left, right, .. }
722        | PcodeOp::BoolOr { left, right, .. }
723        | PcodeOp::BoolXor { left, right, .. }
724        | PcodeOp::FloatAdd { left, right, .. }
725        | PcodeOp::FloatSub { left, right, .. }
726        | PcodeOp::FloatMult { left, right, .. }
727        | PcodeOp::FloatDiv { left, right, .. }
728        | PcodeOp::FloatEq { left, right, .. }
729        | PcodeOp::FloatNotEq { left, right, .. }
730        | PcodeOp::FloatLess { left, right, .. }
731        | PcodeOp::FloatLessEq { left, right, .. } => {
732            f(left);
733            f(right);
734        }
735        PcodeOp::CallOther { inputs, .. } => {
736            for v in inputs {
737                f(v);
738            }
739        }
740    }
741}
742
743fn visit_reads_mut(op: &mut PcodeOp, f: &mut impl FnMut(&mut Varnode)) {
744    match op {
745        PcodeOp::Copy { input, .. } => f(input),
746        PcodeOp::Load { ptr, .. } => f(ptr),
747        PcodeOp::Store { ptr, val, .. } => {
748            f(ptr);
749            f(val);
750        }
751        PcodeOp::Branch { dest }
752        | PcodeOp::BranchInd { dest }
753        | PcodeOp::Call { dest }
754        | PcodeOp::CallInd { dest }
755        | PcodeOp::Return { dest } => f(dest),
756        PcodeOp::CBranch { dest, cond } => {
757            f(dest);
758            f(cond);
759        }
760        PcodeOp::Subpiece { input, .. } => f(input),
761        PcodeOp::IntNeg { input, .. }
762        | PcodeOp::IntNot { input, .. }
763        | PcodeOp::IntZext { input, .. }
764        | PcodeOp::IntSext { input, .. }
765        | PcodeOp::BoolNot { input, .. }
766        | PcodeOp::FloatNeg { input, .. }
767        | PcodeOp::FloatAbs { input, .. }
768        | PcodeOp::FloatSqrt { input, .. }
769        | PcodeOp::FloatNan { input, .. }
770        | PcodeOp::Int2Float { input, .. }
771        | PcodeOp::Float2Float { input, .. }
772        | PcodeOp::Trunc { input, .. }
773        | PcodeOp::FloatCeil { input, .. }
774        | PcodeOp::FloatFloor { input, .. }
775        | PcodeOp::FloatRound { input, .. }
776        | PcodeOp::Popcount { input, .. }
777        | PcodeOp::Lzcount { input, .. } => f(input),
778        PcodeOp::IntAdd { left, right, .. }
779        | PcodeOp::IntSub { left, right, .. }
780        | PcodeOp::IntMult { left, right, .. }
781        | PcodeOp::IntDiv { left, right, .. }
782        | PcodeOp::IntSDiv { left, right, .. }
783        | PcodeOp::IntRem { left, right, .. }
784        | PcodeOp::IntSRem { left, right, .. }
785        | PcodeOp::IntEq { left, right, .. }
786        | PcodeOp::IntNotEq { left, right, .. }
787        | PcodeOp::IntLess { left, right, .. }
788        | PcodeOp::IntLessEq { left, right, .. }
789        | PcodeOp::IntSLess { left, right, .. }
790        | PcodeOp::IntSLessEq { left, right, .. }
791        | PcodeOp::IntAnd { left, right, .. }
792        | PcodeOp::IntOr { left, right, .. }
793        | PcodeOp::IntXor { left, right, .. }
794        | PcodeOp::IntLsl { left, right, .. }
795        | PcodeOp::IntLsr { left, right, .. }
796        | PcodeOp::IntAsr { left, right, .. }
797        | PcodeOp::IntCarry { left, right, .. }
798        | PcodeOp::IntSCarry { left, right, .. }
799        | PcodeOp::IntSBorrow { left, right, .. }
800        | PcodeOp::BoolAnd { left, right, .. }
801        | PcodeOp::BoolOr { left, right, .. }
802        | PcodeOp::BoolXor { left, right, .. }
803        | PcodeOp::FloatAdd { left, right, .. }
804        | PcodeOp::FloatSub { left, right, .. }
805        | PcodeOp::FloatMult { left, right, .. }
806        | PcodeOp::FloatDiv { left, right, .. }
807        | PcodeOp::FloatEq { left, right, .. }
808        | PcodeOp::FloatNotEq { left, right, .. }
809        | PcodeOp::FloatLess { left, right, .. }
810        | PcodeOp::FloatLessEq { left, right, .. } => {
811            f(left);
812            f(right);
813        }
814        PcodeOp::CallOther { inputs, .. } => {
815            for v in inputs {
816                f(v);
817            }
818        }
819    }
820}
821
822/// A single P-code operation.
823///
824/// Variant naming follows Ghidra's P-code reference.
825/// See: <https://ghidra.re/courses/languages/html/pcoderef.html>
826#[derive(Debug, Clone, PartialEq, Eq)]
827pub enum PcodeOp {
828    // ── Data Movement ──────────────────────────────────────────────
829    Copy {
830        out: Varnode,
831        input: Varnode,
832    },
833    Load {
834        out: Varnode,
835        space: AddressSpaceId,
836        ptr: Varnode,
837    },
838    Store {
839        space: AddressSpaceId,
840        ptr: Varnode,
841        val: Varnode,
842    },
843
844    // ── Branching ──────────────────────────────────────────────────
845    Branch {
846        dest: Varnode,
847    },
848    CBranch {
849        dest: Varnode,
850        cond: Varnode,
851    },
852    BranchInd {
853        dest: Varnode,
854    },
855    Call {
856        dest: Varnode,
857    },
858    CallInd {
859        dest: Varnode,
860    },
861    Return {
862        dest: Varnode,
863    },
864
865    // ── Integer Arithmetic ─────────────────────────────────────────
866    IntAdd {
867        out: Varnode,
868        left: Varnode,
869        right: Varnode,
870    },
871    IntSub {
872        out: Varnode,
873        left: Varnode,
874        right: Varnode,
875    },
876    IntMult {
877        out: Varnode,
878        left: Varnode,
879        right: Varnode,
880    },
881    IntDiv {
882        out: Varnode,
883        left: Varnode,
884        right: Varnode,
885    },
886    IntSDiv {
887        out: Varnode,
888        left: Varnode,
889        right: Varnode,
890    },
891    IntRem {
892        out: Varnode,
893        left: Varnode,
894        right: Varnode,
895    },
896    IntSRem {
897        out: Varnode,
898        left: Varnode,
899        right: Varnode,
900    },
901    IntNeg {
902        out: Varnode,
903        input: Varnode,
904    },
905
906    // ── Integer Comparison ─────────────────────────────────────────
907    IntEq {
908        out: Varnode,
909        left: Varnode,
910        right: Varnode,
911    },
912    IntNotEq {
913        out: Varnode,
914        left: Varnode,
915        right: Varnode,
916    },
917    IntLess {
918        out: Varnode,
919        left: Varnode,
920        right: Varnode,
921    },
922    IntLessEq {
923        out: Varnode,
924        left: Varnode,
925        right: Varnode,
926    },
927    IntSLess {
928        out: Varnode,
929        left: Varnode,
930        right: Varnode,
931    },
932    IntSLessEq {
933        out: Varnode,
934        left: Varnode,
935        right: Varnode,
936    },
937
938    // ── Integer Logical / Bitwise ──────────────────────────────────
939    IntAnd {
940        out: Varnode,
941        left: Varnode,
942        right: Varnode,
943    },
944    IntOr {
945        out: Varnode,
946        left: Varnode,
947        right: Varnode,
948    },
949    IntXor {
950        out: Varnode,
951        left: Varnode,
952        right: Varnode,
953    },
954    IntNot {
955        out: Varnode,
956        input: Varnode,
957    },
958
959    // ── Shift ──────────────────────────────────────────────────────
960    IntLsl {
961        out: Varnode,
962        left: Varnode,
963        right: Varnode,
964    },
965    IntLsr {
966        out: Varnode,
967        left: Varnode,
968        right: Varnode,
969    },
970    IntAsr {
971        out: Varnode,
972        left: Varnode,
973        right: Varnode,
974    },
975
976    // ── Extension / Truncation ─────────────────────────────────────
977    IntZext {
978        out: Varnode,
979        input: Varnode,
980    },
981    IntSext {
982        out: Varnode,
983        input: Varnode,
984    },
985    Subpiece {
986        out: Varnode,
987        input: Varnode,
988        lsb: u32,
989    },
990
991    // ── Carry / Borrow ─────────────────────────────────────────────
992    IntCarry {
993        out: Varnode,
994        left: Varnode,
995        right: Varnode,
996    },
997    IntSCarry {
998        out: Varnode,
999        left: Varnode,
1000        right: Varnode,
1001    },
1002    IntSBorrow {
1003        out: Varnode,
1004        left: Varnode,
1005        right: Varnode,
1006    },
1007
1008    // ── Boolean ────────────────────────────────────────────────────
1009    BoolAnd {
1010        out: Varnode,
1011        left: Varnode,
1012        right: Varnode,
1013    },
1014    BoolOr {
1015        out: Varnode,
1016        left: Varnode,
1017        right: Varnode,
1018    },
1019    BoolXor {
1020        out: Varnode,
1021        left: Varnode,
1022        right: Varnode,
1023    },
1024    BoolNot {
1025        out: Varnode,
1026        input: Varnode,
1027    },
1028
1029    // ── Floating Point Arithmetic ──────────────────────────────────
1030    FloatAdd {
1031        out: Varnode,
1032        left: Varnode,
1033        right: Varnode,
1034    },
1035    FloatSub {
1036        out: Varnode,
1037        left: Varnode,
1038        right: Varnode,
1039    },
1040    FloatMult {
1041        out: Varnode,
1042        left: Varnode,
1043        right: Varnode,
1044    },
1045    FloatDiv {
1046        out: Varnode,
1047        left: Varnode,
1048        right: Varnode,
1049    },
1050    FloatNeg {
1051        out: Varnode,
1052        input: Varnode,
1053    },
1054    FloatAbs {
1055        out: Varnode,
1056        input: Varnode,
1057    },
1058    FloatSqrt {
1059        out: Varnode,
1060        input: Varnode,
1061    },
1062
1063    // ── Floating Point Comparison ──────────────────────────────────
1064    FloatEq {
1065        out: Varnode,
1066        left: Varnode,
1067        right: Varnode,
1068    },
1069    FloatNotEq {
1070        out: Varnode,
1071        left: Varnode,
1072        right: Varnode,
1073    },
1074    FloatLess {
1075        out: Varnode,
1076        left: Varnode,
1077        right: Varnode,
1078    },
1079    FloatLessEq {
1080        out: Varnode,
1081        left: Varnode,
1082        right: Varnode,
1083    },
1084    FloatNan {
1085        out: Varnode,
1086        input: Varnode,
1087    },
1088
1089    // ── Floating Point Conversion ──────────────────────────────────
1090    Int2Float {
1091        out: Varnode,
1092        input: Varnode,
1093    },
1094    Float2Float {
1095        out: Varnode,
1096        input: Varnode,
1097    },
1098    Trunc {
1099        out: Varnode,
1100        input: Varnode,
1101    },
1102    FloatCeil {
1103        out: Varnode,
1104        input: Varnode,
1105    },
1106    FloatFloor {
1107        out: Varnode,
1108        input: Varnode,
1109    },
1110    FloatRound {
1111        out: Varnode,
1112        input: Varnode,
1113    },
1114
1115    // ── Bit Manipulation ───────────────────────────────────────────
1116    Popcount {
1117        out: Varnode,
1118        input: Varnode,
1119    },
1120    Lzcount {
1121        out: Varnode,
1122        input: Varnode,
1123    },
1124
1125    // ── Miscellaneous ──────────────────────────────────────────────
1126    /// User-defined or architecture-specific operation.
1127    CallOther {
1128        out: Option<Varnode>,
1129        func_id: u64,
1130        inputs: Vec<Varnode>,
1131    },
1132}