Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions AGENTS.md

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions crates/kirin-constprop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
//! use kirin_constprop::{ConstProp, ConstPropValue};
//! use kirin_interpreter::engine::CrossStageLinker;
//!
//! let analysis = ConstProp::<Stage, MyConst, MyError>::new(&pipeline)
//! .with_linker(CrossStageLinker);
//! let (result, analysis) = analysis.analyze_by_name("source", "abs", [ConstPropValue::Const(value)])?;
//! use kirin_interpreter::engine::AnalysisResults;
//!
//! let analyzed = ConstProp::<Stage, MyConst, MyError>::new(&pipeline)
//! .with_linker(CrossStageLinker)
//! .analyze_by_name("source", "abs", [ConstPropValue::Const(value)])?;
//! let result = analyzed.response(analyzed.root());
//! ```

mod context;
Expand Down
47 changes: 9 additions & 38 deletions crates/kirin-demand/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
//! needed by a root?*
//!
//! This crate is the sparse backward demand analysis: one [`Demand`] bit per
//! SSA value in each reached callable context, computed by [`analyze_demand`]
//! on the shared sparse backward engine
//! ([`kirin_interpreter::SparseBackwardInterpreter`]).
//! SSA value in each reached callable context, computed by [`DemandAnalysis`],
//! the shared sparse backward engine
//! ([`kirin_interpreter::SparseBackwardInterpreter`]) at the [`Demand`]
//! lattice.
//! Roots are the requested outputs of the analyzed callable, control roots
//! (branch conditions, loop bounds), and impure ordinary statements. Demand
//! crosses supported calls: a demanded call result requests the matching
Expand All @@ -16,23 +17,19 @@
//! there is no structural fallback.
//!
//! ```ignore
//! let result = kirin_demand::analyze_demand(&pipeline, stage, callee, [Demand::Live])?;
//! assert!(result.is_demanded(some_value));
//! use kirin_interpreter::AnalysisResults;
//!
//! let analyzed = DemandAnalysis::<Stage>::new(&pipeline).analyze(stage, callee, [Demand::Live])?;
//! assert!(analyzed.is_demanded(analyzed.root(), some_value));
//! ```

mod context;
mod result;
mod value;

pub use context::{DemandCallCtx, OutputDemandContext};
pub use result::DemandResult;
pub use value::Demand;

use kirin_interpreter::{
Callee, InterpDispatch, InterpreterError, SameStageLinker, SparseBackwardInterpreter,
SparseBackwardTransfer, StageQuery,
};
use kirin_ir::{CompileStage, Pipeline, StageMeta};
use kirin_interpreter::{InterpreterError, SameStageLinker, SparseBackwardInterpreter};

/// The sparse backward demand engine instantiated at the [`Demand`] lattice:
/// strong liveness.
Expand All @@ -43,29 +40,3 @@ pub type DemandAnalysis<
Lk = SameStageLinker,
P = OutputDemandContext,
> = SparseBackwardInterpreter<'ir, S, Demand, E, Lk, P>;

/// Resolve `callee` and run strong liveness (sparse backward demand) from
/// its callable body. Supply one lattice value per result, in result order.
pub fn analyze_demand<'ir, S>(
pipeline: &'ir Pipeline<S>,
stage: CompileStage,
callee: Callee,
result_values: impl IntoIterator<Item = Demand>,
) -> Result<DemandResult, InterpreterError>
where
S: StageMeta
+ StageQuery
+ InterpDispatch<
SparseBackwardTransfer<
'ir,
S,
Demand,
InterpreterError,
SameStageLinker,
OutputDemandContext,
>,
>,
{
let (_, engine) = DemandAnalysis::<S>::new(pipeline).analyze(stage, callee, result_values)?;
DemandResult::from_engine(&engine)
}
87 changes: 0 additions & 87 deletions crates/kirin-demand/src/result.rs

This file was deleted.

98 changes: 63 additions & 35 deletions crates/kirin-demand/tests/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

use kirin::prelude::{GetInfo, ParsePipelineText, Pipeline, SSAValue, StageInfo};
use kirin_arith::Arith;
use kirin_demand::{Demand, analyze_demand};
use kirin_interpreter::{Body, Callee};
use kirin_demand::{Demand, DemandAnalysis};
use kirin_interpreter::{AnalysisResults, Body, Callee};
use kirin_test_languages::ArithFunctionLanguage;

const PROGRAM: &str = r#"
Expand Down Expand Up @@ -103,9 +103,11 @@ fn find_arith<R>(
fn strong_liveness_over_branching_function() {
let pipeline = parse(PROGRAM);
let (caller_stage, callee) = function_root(&pipeline, "main");
let result =
analyze_demand(&pipeline, caller_stage, callee, [Demand::Live]).expect("analysis succeeds");
let (_, Body::CFG(cfg)) = result.root_scope() else {
let result = DemandAnalysis::<_>::new(&pipeline)
.analyze(caller_stage, callee, [Demand::Live])
.expect("analysis succeeds");
let root = result.callable_of(result.root()).expect("root is reached");
let Ok(Body::CFG(cfg)) = root.body(&pipeline) else {
panic!("@main resolves to a CFG body")
};

Expand All @@ -120,36 +122,47 @@ fn strong_liveness_over_branching_function() {
Arith::Add { lhs, result, .. } => Some((SSAValue::from(*result), *lhs)),
_ => None,
});
assert!(!result.is_demanded(dead));
assert!(!result.is_demanded(result.root(), dead));
assert_eq!(add_lhs, x, "the add reads the entry parameter");

// Roots and their transitive demands: return operands, the branch
// condition, and the edge arguments feeding demanded successor params.
assert!(result.is_demanded(cond), "branch condition is a root");
assert!(
result.is_demanded(then_param),
result.is_demanded(result.root(), cond),
"branch condition is a root"
);
assert!(
result.is_demanded(result.root(), then_param),
"ret %a demands ^then's param"
);
assert!(result.is_demanded(else_param), "neg feeds ret in ^else");
assert!(
result.is_demanded(x),
result.is_demanded(result.root(), else_param),
"neg feeds ret in ^else"
);
assert!(
result.is_demanded(result.root(), x),
"both edges pass %x into demanded params"
);

let neg = find_arith(&pipeline, cfg, |op| match op {
Arith::Neg { result, .. } => Some(SSAValue::from(*result)),
_ => None,
});
assert!(result.is_demanded(neg), "ret %neg demands the neg result");
assert!(
result.is_demanded(result.root(), neg),
"ret %neg demands the neg result"
);
}

#[test]
fn unused_successor_block_argument_does_not_keep_edge_arg_live() {
let pipeline = parse(DEAD_EDGE_ARG_PROGRAM);
let (caller_stage, callee) = function_root(&pipeline, "main");
let result =
analyze_demand(&pipeline, caller_stage, callee, [Demand::Live]).expect("analysis succeeds");
let (_, Body::CFG(cfg)) = result.root_scope() else {
let result = DemandAnalysis::<_>::new(&pipeline)
.analyze(caller_stage, callee, [Demand::Live])
.expect("analysis succeeds");
let root = result.callable_of(result.root()).expect("root is reached");
let Ok(Body::CFG(cfg)) = root.body(&pipeline) else {
panic!("@main resolves to a CFG body")
};

Expand All @@ -160,12 +173,15 @@ fn unused_successor_block_argument_does_not_keep_edge_arg_live() {

// `^else` returns `%live` directly (a dominated cross-block use), never
// touching its own parameter — so the `%dead` edge argument stays dead.
assert!(result.is_demanded(live));
assert!(result.is_demanded(cond));
assert!(result.is_demanded(then_param));
assert!(!result.is_demanded(else_param), "^else's param is unused");
assert!(result.is_demanded(result.root(), live));
assert!(result.is_demanded(result.root(), cond));
assert!(result.is_demanded(result.root(), then_param));
assert!(
!result.is_demanded(dead),
!result.is_demanded(result.root(), else_param),
"^else's param is unused"
);
assert!(
!result.is_demanded(result.root(), dead),
"an edge arg feeding an undemanded param stays dead"
);
}
Expand Down Expand Up @@ -211,23 +227,27 @@ specialize @test fn @main(i64, i64) -> i64 cfg {
fn terminator_operands_become_demanded() {
let pipeline = parse(RET_PARAM_PROGRAM);
let (caller_stage, callee) = function_root(&pipeline, "main");
let result =
analyze_demand(&pipeline, caller_stage, callee, [Demand::Live]).expect("analysis succeeds");
let (_, Body::CFG(cfg)) = result.root_scope() else {
let result = DemandAnalysis::<_>::new(&pipeline)
.analyze(caller_stage, callee, [Demand::Live])
.expect("analysis succeeds");
let root = result.callable_of(result.root()).expect("root is reached");
let Ok(Body::CFG(cfg)) = root.body(&pipeline) else {
panic!("@main resolves to a CFG body")
};

let x = block_params(&pipeline, cfg, 0)[0];
assert!(result.is_demanded(x));
assert!(result.is_demanded(result.root(), x));
}

#[test]
fn demanded_result_marks_operands_demanded() {
let pipeline = parse(DEMANDED_RESULT_PROGRAM);
let (caller_stage, callee) = function_root(&pipeline, "main");
let result =
analyze_demand(&pipeline, caller_stage, callee, [Demand::Live]).expect("analysis succeeds");
let (_, Body::CFG(cfg)) = result.root_scope() else {
let result = DemandAnalysis::<_>::new(&pipeline)
.analyze(caller_stage, callee, [Demand::Live])
.expect("analysis succeeds");
let root = result.callable_of(result.root()).expect("root is reached");
let Ok(Body::CFG(cfg)) = root.body(&pipeline) else {
panic!("@main resolves to a CFG body")
};

Expand All @@ -236,18 +256,20 @@ fn demanded_result_marks_operands_demanded() {
Arith::Add { result, .. } => Some(SSAValue::from(*result)),
_ => None,
});
assert!(result.is_demanded(sum));
assert!(result.is_demanded(params[0]));
assert!(result.is_demanded(params[1]));
assert!(result.is_demanded(result.root(), sum));
assert!(result.is_demanded(result.root(), params[0]));
assert!(result.is_demanded(result.root(), params[1]));
}

#[test]
fn dead_result_leaves_operands_dead() {
let pipeline = parse(DEAD_RESULT_PROGRAM);
let (caller_stage, callee) = function_root(&pipeline, "main");
let result =
analyze_demand(&pipeline, caller_stage, callee, [Demand::Live]).expect("analysis succeeds");
let (_, Body::CFG(cfg)) = result.root_scope() else {
let result = DemandAnalysis::<_>::new(&pipeline)
.analyze(caller_stage, callee, [Demand::Live])
.expect("analysis succeeds");
let root = result.callable_of(result.root()).expect("root is reached");
let Ok(Body::CFG(cfg)) = root.body(&pipeline) else {
panic!("@main resolves to a CFG body")
};

Expand All @@ -256,10 +278,16 @@ fn dead_result_leaves_operands_dead() {
Arith::Add { result, .. } => Some(SSAValue::from(*result)),
_ => None,
});
assert!(!result.is_demanded(sum), "the add result is never used");
assert!(result.is_demanded(params[0]), "ret %a demands the param");
assert!(
!result.is_demanded(params[1]),
!result.is_demanded(result.root(), sum),
"the add result is never used"
);
assert!(
result.is_demanded(result.root(), params[0]),
"ret %a demands the param"
);
assert!(
!result.is_demanded(result.root(), params[1]),
"a pure statement with a dead result demands nothing"
);
}
Loading
Loading