weirflow 0.1.0

GPU-first dataflow analysis primitives for Vyre and Santh compiler pipelines.
Documentation
#![allow(clippy::too_many_arguments)]

use super::super::{
    sizing, DirectIfdsEntry, DirectIfdsKey, DirectResidentIfdsBatch, DirectResidentIfdsGraph,
    DirectResidentIfdsGraphKey,
};
use crate::ifds_gpu::IfdsResidentDispatch;
use crate::ifds_resident_direct_prepare::prepare_ifds_csr_resident_direct_with_scratch_via;

impl<R> DirectResidentIfdsBatch<R>
where
    R: Clone,
{
    /// Solve many seed sets with a precomputed graph key into caller-owned
    /// result storage.
    ///
    /// This is the allocation-aware hot-path entrypoint: `results` keeps its
    /// outer vector and per-query result-slot capacities across repeated
    /// solves.
    #[allow(clippy::too_many_arguments)]
    pub fn solve_many_with_graph_key_into<D>(
        &mut self,
        dispatch: &D,
        graph_key: DirectResidentIfdsGraphKey,
        num_procs: u32,
        blocks_per_proc: u32,
        facts_per_proc: u32,
        intra_edges: &[(u32, u32, u32)],
        inter_edges: &[(u32, u32, u32, u32)],
        flow_gen: &[(u32, u32, u32)],
        flow_kill: &[(u32, u32, u32)],
        seed_sets: &[&[(u32, u32, u32)]],
        max_iterations: u32,
        results: &mut Vec<Vec<u32>>,
    ) -> Result<(), String>
    where
        D: IfdsResidentDispatch<Resource = R>,
    {
        if seed_sets.is_empty() {
            results.clear();
            return Ok(());
        }
        if seed_sets.iter().all(|seed_facts| seed_facts.is_empty()) {
            crate::dispatch_decode::require_positive_iterations(
                "weir direct resident IFDS solve with precomputed graph key into",
                max_iterations,
            )?;
            super::resize_and_clear_result_slots(results, seed_sets.len())?;
            return Ok(());
        }
        graph_key.validate_edges(
            num_procs,
            blocks_per_proc,
            facts_per_proc,
            intra_edges,
            inter_edges,
            flow_gen,
            flow_kill,
        )?;
        self.solve_many_validated_graph_key_into(
            dispatch,
            graph_key,
            intra_edges,
            inter_edges,
            flow_gen,
            flow_kill,
            seed_sets,
            max_iterations,
            results,
        )
    }

    /// Solve many seed sets with a borrowed graph view into caller-owned
    /// result storage.
    ///
    /// The graph view hashes edge families once when it is constructed. The
    /// borrow keeps those edge slices immutable for repeated solves, so this
    /// path does not re-hash the invariant graph before every resident-cache
    /// lookup.
    pub fn solve_many_with_graph_view_into<D>(
        &mut self,
        dispatch: &D,
        graph: DirectResidentIfdsGraph<'_>,
        seed_sets: &[&[(u32, u32, u32)]],
        max_iterations: u32,
        results: &mut Vec<Vec<u32>>,
    ) -> Result<(), String>
    where
        D: IfdsResidentDispatch<Resource = R>,
    {
        if seed_sets.is_empty() {
            results.clear();
            return Ok(());
        }
        if seed_sets.iter().all(|seed_facts| seed_facts.is_empty()) {
            crate::dispatch_decode::require_positive_iterations(
                "weir direct resident IFDS solve with graph view into",
                max_iterations,
            )?;
            super::resize_and_clear_result_slots(results, seed_sets.len())?;
            return Ok(());
        }
        self.solve_many_validated_graph_key_into(
            dispatch,
            graph.key,
            graph.intra_edges,
            graph.inter_edges,
            graph.flow_gen,
            graph.flow_kill,
            seed_sets,
            max_iterations,
            results,
        )
    }

    pub(super) fn solve_many_validated_graph_key_into<D>(
        &mut self,
        dispatch: &D,
        graph_key: DirectResidentIfdsGraphKey,
        intra_edges: &[(u32, u32, u32)],
        inter_edges: &[(u32, u32, u32, u32)],
        flow_gen: &[(u32, u32, u32)],
        flow_kill: &[(u32, u32, u32)],
        seed_sets: &[&[(u32, u32, u32)]],
        max_iterations: u32,
        results: &mut Vec<Vec<u32>>,
    ) -> Result<(), String>
    where
        D: IfdsResidentDispatch<Resource = R>,
    {
        let key = DirectIfdsKey {
            backend_id: dispatch.resident_backend_id(),
            backend_version: dispatch.resident_backend_version(),
            num_procs: graph_key.num_procs,
            blocks_per_proc: graph_key.blocks_per_proc,
            facts_per_proc: graph_key.facts_per_proc,
            graph_hash: graph_key.graph_hash,
        };
        if self.entries.contains_key(&key) {
            self.hits = self
                .hits
                .checked_add(1)
                .ok_or_else(|| {
                    "weir direct resident IFDS hit counter overflowed u64. Fix: rebuild the direct resident batch facade before reuse."
                        .to_string()
                })?;
            let retained_bytes = self
                .entries
                .get(&key)
                .ok_or_else(|| {
                    "weir direct resident IFDS batch cache lookup failed before hit telemetry"
                        .to_string()
                })?
                .retained_bytes;
            self.record_warm_graph_reuse(retained_bytes)?;
            self.touch_key(&key)?;
            let resident = &self
                .entries
                .get(&key)
                .ok_or_else(|| {
                    "weir direct resident IFDS batch cache lookup failed after hit".to_string()
                })?
                .graph;
            if seed_sets.iter().all(|seed_facts| seed_facts.is_empty()) {
                crate::dispatch_decode::require_positive_iterations(
                    "weir direct resident IFDS solve_cached with graph key into",
                    max_iterations,
                )?;
                super::resize_and_clear_result_slots(results, seed_sets.len())?;
                return Ok(());
            }
            self.solver.solve_resident_prepared_many_into(
                dispatch,
                resident,
                seed_sets,
                max_iterations,
                results,
            )?;
            self.record_resident_solve_batch(seed_sets)?;
            return Ok(());
        }

        self.misses = self
            .misses
            .checked_add(1)
            .ok_or_else(|| {
                "weir direct resident IFDS miss counter overflowed u64. Fix: rebuild the direct resident batch facade before reuse."
                    .to_string()
            })?;
        let retained_bytes = sizing::direct_retained_graph_bytes(
            graph_key.num_procs,
            graph_key.blocks_per_proc,
            graph_key.facts_per_proc,
            intra_edges.len(),
            inter_edges.len(),
            flow_gen.len(),
        )?;
        if let Some(max_retained_bytes) = self
            .max_retained_bytes
            .filter(|max_retained_bytes| retained_bytes > *max_retained_bytes)
        {
            return Err(format!(
                "weir direct resident IFDS CSR requires {retained_bytes} retained bytes but the configured device-memory budget allows {max_retained_bytes}. Fix: increase max_retained_bytes, shard the IFDS graph, or use a smaller fact/block domain before direct resident prepare."
            ));
        }
        self.evict_until_room(dispatch, retained_bytes)?;
        self.reserve_cache_admission_slot()?;
        let resident = prepare_ifds_csr_resident_direct_with_scratch_via(
            dispatch,
            graph_key.num_procs,
            graph_key.blocks_per_proc,
            graph_key.facts_per_proc,
            intra_edges,
            inter_edges,
            flow_gen,
            flow_kill,
            &mut self.prepare_scratch,
            &mut self.prepare_resource_scratch,
        )?;
        self.direct_prepares = self
            .direct_prepares
            .checked_add(1)
            .ok_or_else(|| {
                "weir direct resident IFDS prepare counter overflowed u64. Fix: rebuild the direct resident batch facade before reuse."
                    .to_string()
            })?;
        self.direct_prepare_bytes = self
            .direct_prepare_bytes
            .checked_add(
                u64::try_from(retained_bytes)
                    .map_err(|_| {
                        "weir direct resident IFDS retained byte count exceeded u64. Fix: shard the IFDS graph before direct resident prepare."
                            .to_string()
                    })?,
            )
            .ok_or_else(|| {
                "weir direct resident IFDS prepare byte counter overflowed u64. Fix: rebuild the direct resident batch facade before reuse."
                    .to_string()
            })?;
        self.record_cold_graph_prepare(retained_bytes)?;
        self.entries.insert(
            key.clone(),
            DirectIfdsEntry {
                retained_bytes,
                last_seen: 0,
                graph: resident,
            },
        );
        self.retained_bytes = self
            .retained_bytes
            .checked_add(retained_bytes)
            .ok_or_else(|| {
                "weir direct resident IFDS retained byte accounting overflowed usize. Fix: reduce retained graph budget or rebuild the direct resident batch facade."
                    .to_string()
            })?;
        self.touch_key(&key)?;

        let resident = &self
            .entries
            .get(&key)
            .ok_or_else(|| {
                "weir direct resident IFDS batch cache lookup failed after prepare".to_string()
            })?
            .graph;
        if seed_sets.iter().all(|seed_facts| seed_facts.is_empty()) {
            crate::dispatch_decode::require_positive_iterations(
                "weir direct resident IFDS solve_with_graph_key_into after prepare",
                max_iterations,
            )?;
            super::resize_and_clear_result_slots(results, seed_sets.len())?;
            return Ok(());
        }
        self.solver.solve_resident_prepared_many_into(
            dispatch,
            resident,
            seed_sets,
            max_iterations,
            results,
        )?;
        self.record_resident_solve_batch(seed_sets)
    }
}