pub mod canonical;
pub mod fusion;
pub mod kind;
pub mod observer;
pub mod pipeline;
pub mod preserved;
pub use canonical::{Canonical, Raw};
pub use fusion::{FusionGroup, Pipeline};
pub use kind::{Fusability, PassKind};
pub use observer::{
AnalysisEvent, BudgetObserver, FailEvent, NoObserver, NoRemarks, Pair, PassEvent, PassFailure,
PassObserver, PassRemarks, Remark, RemarkArg, RemarkCollector, RemarkCounter, RemarkKind,
RemarkSink, RemarkValue, WalkTiming, run_pipeline, run_pipeline_remarked,
};
pub use pipeline::{PipelineSyntaxError, parse_pipelines, print_pipelines};
pub use preserved::{AnalysisId, MAX_ANALYSES, Preserved};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PassDesc {
pub name: &'static str,
pub kind: PassKind,
pub fusability: Fusability,
pub preserved: Preserved,
}
impl PassDesc {
#[must_use]
pub const fn new(
name: &'static str,
kind: PassKind,
fusability: Fusability,
preserved: Preserved,
) -> Self {
assert!(!name.is_empty(), "a pass must have a name");
assert!(
!(kind.is_mandatory() && fusability.is_fusable()),
"a mandatory pass must be a barrier: it either needs whole-tree facts \
or establishes an invariant the next pass reads, and fusion breaks both"
);
Self {
name,
kind,
fusability,
preserved,
}
}
#[inline]
#[must_use]
pub const fn is_mandatory(&self) -> bool {
self.kind.is_mandatory()
}
}
#[cfg(target_pointer_width = "64")]
const _: () = assert!(size_of::<PassDesc>() == 32);
pub trait Pass {
const DESC: PassDesc;
}
#[cfg(test)]
mod tests {
use super::{Fusability, Pass, PassDesc, PassKind, Pipeline, Preserved};
const SCOPES: super::AnalysisId = super::AnalysisId::new(0);
const CONSTNESS: super::AnalysisId = super::AnalysisId::new(1);
const NORMALIZE: PassDesc = PassDesc::new(
"normalize",
PassKind::Optional,
Fusability::Fusable,
Preserved::ALL,
);
const FOLD: PassDesc = PassDesc::new(
"fold",
PassKind::Optional,
Fusability::Fusable,
Preserved::NONE.with(SCOPES).with(CONSTNESS),
);
const HOIST: PassDesc = PassDesc::new(
"hoist",
PassKind::Optional,
Fusability::Fusable,
Preserved::NONE.with(SCOPES),
);
const CHECK: PassDesc = PassDesc::new(
"check",
PassKind::MandatoryDiagnostic,
Fusability::Barrier,
Preserved::ALL,
);
struct Normalizer;
impl Pass for Normalizer {
const DESC: PassDesc = NORMALIZE;
}
#[test]
fn a_description_keeps_what_it_was_given() {
assert_eq!(NORMALIZE.name, "normalize");
assert_eq!(NORMALIZE.kind, PassKind::Optional);
assert_eq!(NORMALIZE.fusability, Fusability::Fusable);
assert!(!NORMALIZE.is_mandatory());
assert!(CHECK.is_mandatory());
}
#[test]
fn a_pass_reaches_its_description_through_the_trait() {
assert_eq!(Normalizer::DESC, NORMALIZE);
}
#[test]
fn every_mandatory_pass_is_a_barrier() {
for desc in [NORMALIZE, FOLD, HOIST, CHECK] {
if desc.is_mandatory() {
assert_eq!(
desc.fusability,
Fusability::Barrier,
"{} is mandatory and must be a barrier",
desc.name
);
}
}
}
#[test]
fn the_fixture_pipeline_groups_are_const_pinned() {
const PASSES: &[PassDesc] = &[NORMALIZE, FOLD, CHECK, HOIST];
const PIPELINE: Pipeline = Pipeline::new("s2", PASSES);
const _: () = assert!(PIPELINE.group_count() == 3);
const FIRST: super::FusionGroup = match PIPELINE.group(0) {
Some(group) => group,
None => panic!("group 0 exists"),
};
const _: () = assert!(FIRST.start == 0 && FIRST.len == 2 && !FIRST.is_barrier);
const _: () = assert!(FIRST.preserved.preserves(SCOPES));
const _: () = assert!(FIRST.preserved.preserves(CONSTNESS));
const SECOND: super::FusionGroup = match PIPELINE.group(1) {
Some(group) => group,
None => panic!("group 1 exists"),
};
const _: () = assert!(SECOND.start == 2 && SECOND.len == 1 && SECOND.is_barrier);
const THIRD: super::FusionGroup = match PIPELINE.group(2) {
Some(group) => group,
None => panic!("group 2 exists"),
};
const _: () = assert!(THIRD.start == 3 && THIRD.len == 1 && !THIRD.is_barrier);
const _: () = assert!(PIPELINE.group(3).is_none());
assert_eq!(PIPELINE.group_count(), 3);
}
#[test]
fn a_mandatory_pass_never_joins_a_fusion_group() {
const ARRANGEMENTS: [&[PassDesc]; 5] = [
&[CHECK, NORMALIZE, FOLD],
&[NORMALIZE, CHECK, FOLD],
&[NORMALIZE, FOLD, CHECK],
&[CHECK, CHECK],
&[CHECK],
];
for passes in ARRANGEMENTS {
let pipeline = Pipeline::new("s2", passes);
for group in pipeline.groups() {
let members = pipeline.passes_of(group);
let mandatory = members.iter().filter(|desc| desc.is_mandatory()).count();
if mandatory > 0 {
assert_eq!(
members.len(),
1,
"a group holding a mandatory pass must hold nothing else"
);
assert!(group.is_barrier);
}
}
}
}
#[test]
fn a_pipeline_of_only_barriers_is_fully_serialized() {
const PASSES: &[PassDesc] = &[CHECK, CHECK, CHECK];
const PIPELINE: Pipeline = Pipeline::new("s2", PASSES);
const _: () = assert!(PIPELINE.is_fully_serialized());
assert_eq!(PIPELINE.group_count(), 3);
}
#[test]
fn an_empty_pipeline_costs_no_walks() {
const PIPELINE: Pipeline = Pipeline::new("s2", &[]);
const _: () = assert!(PIPELINE.group_count() == 0);
assert!(PIPELINE.group(0).is_none());
assert!(PIPELINE.group_of_pass(0).is_none());
}
#[test]
fn every_pass_reports_the_group_it_landed_in() {
const PASSES: &[PassDesc] = &[NORMALIZE, FOLD, CHECK, HOIST];
const PIPELINE: Pipeline = Pipeline::new("s2", PASSES);
assert_eq!(PIPELINE.group_of_pass(0), Some(0));
assert_eq!(PIPELINE.group_of_pass(1), Some(0));
assert_eq!(PIPELINE.group_of_pass(2), Some(1));
assert_eq!(PIPELINE.group_of_pass(3), Some(2));
assert_eq!(PIPELINE.group_of_pass(4), None);
}
}