neo-decompiler 0.10.1

Neo N3 NEF decompiler: parse, disassemble, lift bytecode to high-level pseudocode and C# skeletons, with a CLI, JSON reports, and optional WebAssembly bindings.
Documentation
use std::fmt;

use crate::decompiler::cfg::BlockId;

use super::SsaVariable;

/// A φ (phi) node representing a merge point in the CFG.
///
/// φ nodes are inserted at control flow merge points (dominance frontiers) to
/// ensure the single-assignment property. Each φ node selects a value based on
/// which predecessor block was executed.
///
/// # Structure
///
/// ```text
///     block1    block2
///       |          |
///       v          v
///        \        /
///         merge_block
///         x3 = φ(x1, x2)
/// ```
///
/// At runtime, `x3` takes the value of `x1` if control came from `block1`, or
/// `x2` if it came from `block2`.
///
/// # Internal Use Only
///
/// φ nodes are used internally for analysis and are typically transformed away
/// before rendering output. Users should not see raw φ nodes in decompiled code.
///
/// # Examples
///
/// ```
/// use neo_decompiler::decompiler::cfg::ssa::{PhiNode, SsaVariable};
/// use neo_decompiler::decompiler::cfg::BlockId;
///
/// let target = SsaVariable::initial("result".to_string());
/// let mut phi = PhiNode::new(target.clone());
///
/// let block1 = BlockId::from(1);
/// let block2 = BlockId::from(2);
///
/// phi.add_operand(block1, SsaVariable::new("x".to_string(), 0));
/// phi.add_operand(block2, SsaVariable::new("x".to_string(), 1));
///
/// assert_eq!(phi.operands.len(), 2);
/// ```
#[derive(Debug, Clone, PartialEq)]
pub struct PhiNode {
    /// The target SSA variable being defined by this φ node.
    pub target: SsaVariable,
    /// Operands from each predecessor block.
    ///
    /// Maps predecessor block IDs to the SSA variable that flows from that edge.
    pub operands: std::collections::BTreeMap<BlockId, SsaVariable>,
}

impl PhiNode {
    /// Create a new φ node with an empty operand list.
    #[must_use]
    pub const fn new(target: SsaVariable) -> Self {
        Self {
            target,
            operands: std::collections::BTreeMap::new(),
        }
    }

    /// Add an operand from a predecessor block.
    ///
    /// # Arguments
    ///
    /// * `predecessor` - The block ID of the predecessor.
    /// * `var` - The SSA variable that reaches this φ node from that predecessor.
    pub fn add_operand(&mut self, predecessor: BlockId, var: SsaVariable) {
        self.operands.insert(predecessor, var);
    }

    /// Get the number of operands (predecessors).
    #[must_use]
    pub fn operand_count(&self) -> usize {
        self.operands.len()
    }
}

impl fmt::Display for PhiNode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} = φ(", self.target)?;
        let mut first = true;
        for (block, var) in &self.operands {
            if !first {
                write!(f, ", ")?;
            }
            write!(f, "{:?}: {}", block, var)?;
            first = false;
        }
        write!(f, ")")
    }
}