unluac 1.0.0

Multi-dialect Lua decompiler written in Rust.
Documentation
//! 这个文件提取普通 branch 的值合流候选。
//!
//! 这个 pass 依赖 CFG / GraphFacts / Dataflow 已经给好的 branch 骨架和 phi 事实,
//! 负责把“结构臂归属 + HIR 真正要用的 def 身份”一次性前移到 StructureFacts。
//! 它不会越权做 decision/alias 最终选择,那一步仍留给 HIR。
//!
//! 例子:
//! - `if cond then x = 1 else x = 2 end` 会把 merge phi 记录成
//!   `then_arm = {preds, defs_of_1}`、`else_arm = {preds, defs_of_2}`
//! - 这样 HIR 只消费 `then/else` 两臂已经分好的 defs,不再自己回头拆 `phi.incoming`

use std::collections::BTreeSet;

use crate::cfg::{BlockRef, Cfg, DataflowFacts, GraphFacts};

use super::common::{
    BranchKind, BranchRegionFact, BranchValueMergeCandidate, ShortCircuitCandidate,
    ShortCircuitExit,
};
use super::helpers::collect_merge_arm_preds;
use super::phi_facts::branch_value_merges_in_block;

pub(super) fn analyze_branch_value_merges(
    cfg: &Cfg,
    graph_facts: &GraphFacts,
    dataflow: &DataflowFacts,
    branch_regions: &[BranchRegionFact],
    short_circuit_candidates: &[ShortCircuitCandidate],
) -> Vec<BranchValueMergeCandidate> {
    let short_circuit_merges = short_circuit_candidates
        .iter()
        .filter_map(|candidate| match candidate.exit {
            ShortCircuitExit::ValueMerge(merge) => {
                Some((candidate.header, merge, candidate.result_reg))
            }
            ShortCircuitExit::BranchExit { .. } => None,
        })
        .collect::<BTreeSet<_>>();

    let mut candidates = Vec::new();

    for branch_region in branch_regions {
        let Some(candidate) =
            analyze_branch_value_merge_candidate(dataflow, branch_region, &short_circuit_merges)
        else {
            continue;
        };
        candidates.push(candidate);
    }

    candidates.extend(analyze_guard_short_circuit_branch_value_merges(
        cfg,
        graph_facts,
        dataflow,
        short_circuit_candidates,
    ));

    candidates.sort_by_key(|candidate| (candidate.header, candidate.merge));
    candidates
}

fn analyze_guard_short_circuit_branch_value_merges(
    cfg: &Cfg,
    graph_facts: &GraphFacts,
    dataflow: &DataflowFacts,
    short_circuit_candidates: &[ShortCircuitCandidate],
) -> Vec<BranchValueMergeCandidate> {
    let mut candidates = Vec::new();

    for short in short_circuit_candidates {
        let ShortCircuitExit::BranchExit { truthy, falsy } = short.exit else {
            continue;
        };
        if !cfg.can_reach(truthy, falsy) || cfg.can_reach(falsy, truthy) {
            continue;
        }

        let then_preds = collect_merge_arm_preds(cfg, &graph_facts.dominator_tree, truthy, falsy);
        let else_preds = short.branch_exit_leaf_preds(false);
        if then_preds.is_empty() || else_preds.is_empty() || !then_preds.is_disjoint(&else_preds) {
            continue;
        }

        let values =
            branch_value_merges_in_block(short.header, dataflow, falsy, &then_preds, &else_preds);

        if !values.is_empty() {
            candidates.push(BranchValueMergeCandidate {
                header: short.header,
                merge: falsy,
                values,
            });
        }
    }

    candidates
}

fn analyze_branch_value_merge_candidate(
    dataflow: &DataflowFacts,
    branch_region: &BranchRegionFact,
    short_circuit_merges: &BTreeSet<(BlockRef, BlockRef, Option<crate::transformer::Reg>)>,
) -> Option<BranchValueMergeCandidate> {
    if branch_region.kind != BranchKind::IfElse {
        return None;
    }

    let merge = branch_region.merge;
    let then_preds = &branch_region.then_merge_preds;
    let else_preds = &branch_region.else_merge_preds;
    if then_preds.is_empty() || else_preds.is_empty() || !then_preds.is_disjoint(else_preds) {
        return None;
    }

    let values = branch_value_merges_in_block(
        branch_region.header,
        dataflow,
        merge,
        then_preds,
        else_preds,
    )
    .into_iter()
    .filter(|value| !short_circuit_merges.contains(&(branch_region.header, merge, Some(value.reg))))
    .collect::<Vec<_>>();

    (!values.is_empty()).then_some(BranchValueMergeCandidate {
        header: branch_region.header,
        merge,
        values,
    })
}