weirflow 0.1.0

GPU-first dataflow analysis primitives for Vyre and Santh compiler pipelines.
Documentation
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::Path;

use surge_frontend::pipeline::classifier::FamilyClassifier;
use surge_frontend::pipeline::language::PipelineLanguage;
use surge_frontend::pipeline::produce;

use vyre_primitives::graph::program_graph::ProgramGraphShape;
use weir::callgraph::{callgraph_build, callgraph_build_with_count};
use weir::control_dependence::control_dependence;
use weir::cross_language::cross_language;
use weir::dominators::dominates;
use weir::escape::escape_analyze_with_count;
use weir::escapes::escapes;
use weir::graph_layout::CsrGraph;
use weir::ifds::ifds_reach_step;
use weir::live::prepare_live_graph;
use weir::live_at::live_at;
use weir::loop_sum::{loop_summarize, try_loop_summarize_with_count};
use weir::may_alias::may_alias;
use weir::must_init::must_init;
use weir::points_to::prepare_points_to_subset_graph;
use weir::post_dominates::post_dominates;
use weir::range::{range_propagate, try_range_propagate_with_count};
use weir::range_check::range_check;
use weir::reaching::prepare_reaching_graph;
use weir::reaching_def::reaching_def;
use weir::scc_query::scc_query;
use weir::slice::prepare_slice_graph;
use weir::ssa::{try_compute_dominators, Block, Cfg};
use weir::summary::{summarize_function, summarize_function_with_count};
use weir::traversal_step::forward_cfg_step;
use weir::value_set::value_set;

#[test]
fn cross_language_matrix() {
    let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
    let df_micro = manifest_dir.join("tests").join("df_micro");

    let mut c_files: Vec<_> = fs::read_dir(&df_micro)
        .expect("read df_micro")
        .filter_map(|e| e.ok())
        .filter(|e| e.path().extension().map(|ext| ext == "c").unwrap_or(false))
        .map(|e| e.path())
        .collect();
    c_files.sort();

    assert!(
        c_files.len() >= 100,
        "expected at least 100 .c files, found {}",
        c_files.len()
    );

    let cls = FamilyClassifier::launch_defaults();

    for path in &c_files {
        let source = fs::read(path).unwrap_or_else(|_| panic!("read {}", path.display()));
        let pg = produce(PipelineLanguage::C, &source, &cls);

        assert!(
            pg.node_count > 0 || pg.edge_count == 0,
            "{}: parsed zero nodes but non-zero edges is suspicious",
            path.display()
        );

        if pg.node_count == 0 {
            continue;
        }

        // Build CSR from flat edges.
        let mut edges: Vec<(u32, u32, u32)> = pg.iter_edges().collect();
        edges.sort_by_key(|(f, t, _k)| (*f, *t));
        let n = pg.node_count as usize;
        let mut offsets = vec![0u32; n + 1];
        let mut targets = Vec::new();
        let mut kinds = Vec::new();
        let mut succs: Vec<Vec<u32>> = vec![Vec::new(); n];
        for (f, t, k) in &edges {
            offsets[(*f as usize) + 1] += 1;
            targets.push(*t);
            kinds.push(*k);
            succs[*f as usize].push(*t);
        }
        for i in 1..=n {
            offsets[i] += offsets[i - 1];
        }

        let shape = ProgramGraphShape::new(pg.node_count, pg.edge_count);

        // Graph layout / CSR normalization
        let normalized = CsrGraph::new(pg.node_count, &offsets, &targets, &kinds)
            .normalize("test")
            .expect("normalize should succeed for valid CSR");
        assert_eq!(normalized.node_count(), pg.node_count);

        // SSA dominators on synthetic CFG built from AST edges
        let mut blocks = HashMap::new();
        for i in 0..pg.node_count {
            blocks.insert(
                i,
                Block {
                    id: i,
                    preds: Vec::new(),
                    succs: succs[i as usize].clone(),
                    defs: HashSet::new(),
                    uses: HashSet::new(),
                },
            );
        }
        for i in 0..pg.node_count {
            let succ_list = succs[i as usize].clone();
            for &s in &succ_list {
                if let Some(b) = blocks.get_mut(&s) {
                    b.preds.push(i);
                }
            }
        }
        let cfg = Cfg { entry: 0, blocks };
        let _ = try_compute_dominators(&cfg);

        // Bitset query Programs
        let prog = dominates(pg.node_count, "dom", "target", "out");
        assert!(!prog.entry().is_empty());

        let prog = post_dominates(pg.node_count, "pdom_set", "target_set", "out");
        assert!(!prog.entry().is_empty());

        let prog = control_dependence(pg.node_count, "succ_pdom", "pdom_n", "out");
        assert!(!prog.entry().is_empty());

        let prog = escapes(pg.node_count, "pts", "escape_set", "out");
        assert!(!prog.entry().is_empty());

        let prog = may_alias(pg.node_count, "pts_p", "pts_q", "scratch", "out_scalar");
        assert!(!prog.entry().is_empty());

        let prog = range_check(pg.node_count, "interval_hi", "bound", "out");
        assert!(!prog.entry().is_empty());

        let prog = value_set(pg.node_count, "const_in", "query", "out");
        assert!(!prog.entry().is_empty());

        let prog = scc_query(pg.node_count, "same_scc", "query", "out");
        assert!(!prog.entry().is_empty());

        let prog = must_init(pg.node_count, "def_dominates", "use_set", "out");
        assert!(!prog.entry().is_empty());

        let prog = reaching_def(pg.node_count, "gen_kill_in", "use_set", "out");
        assert!(!prog.entry().is_empty());

        let prog = live_at(pg.node_count, "live_in", "query", "out");
        assert!(!prog.entry().is_empty());

        // Callgraph / escape / loop / range / summary Programs
        let prog = callgraph_build("direct", "indirect", "pts", "cg_out");
        assert!(!prog.entry().is_empty());

        let prog = callgraph_build_with_count("direct", "indirect", "pts", "cg_out", pg.node_count);
        assert!(!prog.entry().is_empty());

        let prog = escape_analyze_with_count("pts", "cg", "out", pg.node_count);
        assert!(!prog.entry().is_empty());

        let prog = loop_summarize("prev", "next", "out");
        assert!(!prog.entry().is_empty());

        let prog = try_loop_summarize_with_count("prev", "next", "out", pg.node_count)
            .expect("loop_summarize_with_count should build for parsed node count");
        assert!(!prog.entry().is_empty());

        let prog = range_propagate("defs_in", "edges_in", "ranges_out");
        assert!(!prog.entry().is_empty());

        let prog =
            try_range_propagate_with_count("defs_in", "edges_in", "ranges_out", pg.node_count)
                .expect("range_propagate_with_count should build for parsed node count");
        assert!(!prog.entry().is_empty());

        let prog = summarize_function("fn_ast", "callgraph", "cached", "summary");
        assert!(!prog.entry().is_empty());

        let prog = summarize_function_with_count(
            "fn_ast",
            "callgraph",
            "cached",
            "summary",
            pg.node_count,
        );
        assert!(!prog.entry().is_empty());

        // Cross-language
        let prog = cross_language(
            pg.node_count,
            "source",
            "sink",
            "post_cross",
            "current",
            "next",
            "changed",
            "seed",
            "out",
        );
        assert!(
            prog.is_ok(),
            "cross_language construction failed: {:?}",
            prog.err()
        );
        assert!(!prog.unwrap().entry().is_empty());

        // Traversal / IFDS steps
        let prog = forward_cfg_step(shape, "fin", "fout");
        assert!(!prog.entry().is_empty());

        let prog = ifds_reach_step(shape, "fin", "fout");
        assert!(!prog.entry().is_empty());

        // Fixed-point graph preparation
        let g = prepare_reaching_graph(pg.node_count, &offsets, &targets, &kinds);
        assert!(g.is_ok(), "prepare_reaching_graph failed: {:?}", g.err());
        assert_eq!(g.unwrap().node_count(), pg.node_count);

        let g = prepare_live_graph(pg.node_count, &offsets, &targets, &kinds);
        assert!(g.is_ok(), "prepare_live_graph failed: {:?}", g.err());
        assert_eq!(g.unwrap().node_count(), pg.node_count);

        let g = prepare_points_to_subset_graph(pg.node_count, &offsets, &targets, &kinds);
        assert!(
            g.is_ok(),
            "prepare_points_to_subset_graph failed: {:?}",
            g.err()
        );
        assert_eq!(g.unwrap().node_count(), pg.node_count);

        let g = prepare_slice_graph(pg.node_count, &offsets, &targets, &kinds);
        assert!(g.is_ok(), "prepare_slice_graph failed: {:?}", g.err());
        assert_eq!(g.unwrap().node_count(), pg.node_count);
    }
}