vyre-debug 0.6.5

Debugging CLI and inspection helpers for Vyre programs, lowering, and emitted shaders.
Documentation
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
use vyre_lower::verify::{classify_operand, OperandClass};
use vyre_lower::{KernelBody, KernelDescriptor};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DanglingRef {
    pub ref_id: u32,
    pub produced_in_body_path: Vec<usize>,
    pub producing_op_index: usize,
    pub producing_op_kind: String,
    pub referenced_in_body_path: Vec<usize>,
    pub referencing_op_index: usize,
    pub referencing_op_kind: String,
    pub operand_position: usize,
}

pub fn find_dangling_refs(desc: &KernelDescriptor) -> Vec<DanglingRef> {
    let mut id_origins = BTreeMap::new();

    fn build_id_origins(
        body: &KernelBody,
        path: &mut Vec<usize>,
        map: &mut BTreeMap<u32, (Vec<usize>, usize, String)>,
    ) {
        for (i, op) in body.ops.iter().enumerate() {
            for r in op.result_ids() {
                map.insert(r, (path.clone(), i, format!("{:?}", op.kind)));
            }
        }
        for (idx, child) in body.child_bodies.iter().enumerate() {
            path.push(idx);
            build_id_origins(child, path, map);
            path.pop();
        }
    }
    build_id_origins(&desc.body, &mut vec![], &mut id_origins);

    let mut refs = Vec::new();
    check_body(
        &desc.body,
        &mut vec![],
        &BTreeSet::new(),
        &mut refs,
        &id_origins,
    );
    refs
}

fn check_body(
    body: &KernelBody,
    path: &mut Vec<usize>,
    inherited_results: &BTreeSet<u32>,
    refs: &mut Vec<DanglingRef>,
    id_origins: &BTreeMap<u32, (Vec<usize>, usize, String)>,
) {
    let mut produced: BTreeSet<u32> = BTreeSet::new();
    for op in &body.ops {
        for r in op.result_ids() {
            produced.insert(r);
        }
    }

    let mut produced_so_far: BTreeSet<u32> = BTreeSet::new();
    let child_results: Vec<BTreeSet<u32>> =
        body.child_bodies.iter().map(collect_body_results).collect();
    let mut completed_child_results: BTreeSet<u32> = BTreeSet::new();
    let mut child_scopes = vec![BTreeSet::new(); body.child_bodies.len()];

    for (i, op) in body.ops.iter().enumerate() {
        for (pos, &val) in op.operands.iter().enumerate() {
            let cls = classify_operand(&op.kind, pos);
            match cls {
                OperandClass::ResultRef => {
                    if !produced_so_far.contains(&val)
                        && !produced.contains(&val)
                        && !inherited_results.contains(&val)
                        && !completed_child_results.contains(&val)
                    {
                        let (prod_path, prod_idx, prod_kind) =
                            if let Some(orig) = id_origins.get(&val) {
                                (orig.0.clone(), orig.1, orig.2.clone())
                            } else {
                                (vec![], 0, "Unknown".to_string())
                            };

                        refs.push(DanglingRef {
                            ref_id: val,
                            produced_in_body_path: prod_path,
                            producing_op_index: prod_idx,
                            producing_op_kind: prod_kind,
                            referenced_in_body_path: path.clone(),
                            referencing_op_index: i,
                            referencing_op_kind: format!("{:?}", op.kind),
                            operand_position: pos,
                        });
                    }
                }
                OperandClass::ChildBodyIdx => {
                    if (val as usize) < body.child_bodies.len() {
                        let child_scope = &mut child_scopes[val as usize];
                        child_scope.extend(inherited_results.iter().copied());
                        child_scope.extend(produced_so_far.iter().copied());
                        child_scope.extend(completed_child_results.iter().copied());
                    }
                }
                _ => {}
            }
        }

        for r in op.result_ids() {
            produced_so_far.insert(r);
        }

        for (pos, &val) in op.operands.iter().enumerate() {
            if classify_operand(&op.kind, pos) == OperandClass::ChildBodyIdx {
                if let Some(results) = child_results.get(val as usize) {
                    completed_child_results.extend(results.iter().copied());
                }
            }
        }
    }

    for (idx, child) in body.child_bodies.iter().enumerate() {
        path.push(idx);
        check_body(child, path, &child_scopes[idx], refs, id_origins);
        path.pop();
    }
}

fn collect_body_results(body: &KernelBody) -> BTreeSet<u32> {
    let mut results = BTreeSet::new();
    for op in &body.ops {
        for result in op.result_ids() {
            results.insert(result);
        }
    }
    for child in &body.child_bodies {
        results.extend(collect_body_results(child));
    }
    results
}