Skip to main content

hugr_passes/
composable.rs

1//! Compiler passes and utilities for composing them.
2//!
3//! The core trait is [`ComposablePass`], which defines a transformation that can
4//! be applied to a HUGR.
5//! See the [`ComposablePass`] trait documentation for more details.
6//!
7
8mod scope;
9
10pub use scope::{InScope, PassScope, Preserve};
11
12use std::{error::Error, marker::PhantomData};
13
14use hugr_core::core::HugrNode;
15use hugr_core::hugr::{ValidationError, hugrmut::HugrMut};
16use itertools::Either;
17
18/// An optimization pass that can be sequenced with another and/or wrapped
19/// e.g. by [`ValidatingPass`].
20///
21/// Note it is expected that (simple) passes should make reasonable effort to be
22/// idempotent (i.e. such that after running a pass, rerunning it immediately has
23/// no further effect). However this is *not* a requirement, e.g. a sequence of
24/// idempotent passes created by [ComposablePass::then] may not be idempotent itself.
25#[deprecated(
26    note = "`hugr-passes` is deprecated. Use tket::passes instead",
27    since = "0.26.2"
28)]
29pub trait ComposablePass<H: HugrMut>: WithScope + Sized {
30    /// Error thrown by this pass.
31    type Error: Error;
32    /// Result returned by this pass.
33    type Result; // Would like to default to () but currently unstable
34
35    /// Run the pass on the given HUGR.
36    fn run(&self, hugr: &mut H) -> Result<Self::Result, Self::Error>;
37
38    /// Apply a function to the error type of this pass, returning a new
39    /// [`ComposablePass`] that has the same result type.
40    fn map_err<E2: Error>(
41        self,
42        f: impl Fn(Self::Error) -> E2,
43    ) -> impl ComposablePass<H, Result = Self::Result, Error = E2> {
44        ErrMapper::new(self, f)
45    }
46
47    /// Returns a [`ComposablePass`] that does "`self` then `other`", so long as
48    /// `other::Err` can be combined with ours.
49    ///
50    /// Composed passes may have different configured [`PassScope`]s. Use
51    /// [`WithScope::with_scope`] after the composition to override all the
52    /// scope configurations if needed.
53    ///
54    /// Note this is not necessarily idempotent even if both `self` and `other` are.
55    /// (Idempotency would require rerunning the sequence of both until no change;
56    /// since there is no general/efficient reporting of whether a pass has changed
57    /// the hugr, no such checking or looping is done here.)
58    fn then<P: ComposablePass<H>, E: ErrorCombiner<Self::Error, P::Error>>(
59        self,
60        other: P,
61    ) -> impl ComposablePass<H, Result = (Self::Result, P::Result), Error = E> {
62        struct Sequence<E, P1, P2>(P1, P2, PhantomData<E>);
63        impl<H, E, P1, P2> ComposablePass<H> for Sequence<E, P1, P2>
64        where
65            H: HugrMut,
66            P1: ComposablePass<H>,
67            P2: ComposablePass<H>,
68            E: ErrorCombiner<P1::Error, P2::Error>,
69        {
70            type Error = E;
71            type Result = (P1::Result, P2::Result);
72
73            fn run(&self, hugr: &mut H) -> Result<Self::Result, Self::Error> {
74                let res1 = self.0.run(hugr).map_err(E::from_first)?;
75                let res2 = self.1.run(hugr).map_err(E::from_second)?;
76                Ok((res1, res2))
77            }
78        }
79        impl<E, P1, P2> WithScope for Sequence<E, P1, P2>
80        where
81            P1: WithScope,
82            P2: WithScope,
83        {
84            fn with_scope(self, scope: impl Into<PassScope>) -> Self {
85                let scope = scope.into();
86                Self(
87                    self.0.with_scope(scope.clone()),
88                    self.1.with_scope(scope),
89                    PhantomData,
90                )
91            }
92        }
93
94        Sequence(self, other, PhantomData)
95    }
96}
97
98/// Extension trait for adding a `with_scope` method to a `ComposablePass` that
99/// does not require instantiating the `H` generic parameter.
100#[deprecated(
101    note = "`hugr-passes` is deprecated. Use tket::passes instead",
102    since = "0.26.2"
103)]
104pub trait WithScope {
105    /// Set the scope configuration used to run the pass.
106    ///
107    /// See [`PassScope`] for more details.
108    ///
109    /// Since `hugr >=0.26.0`, passes must implement this to respect the scope configuration.
110    fn with_scope(self, scope: impl Into<PassScope>) -> Self;
111
112    /// Return a default instance of the pass with the given scope.
113    ///
114    /// See [`PassScope`] for more details.
115    #[must_use]
116    fn default_with_scope(scope: PassScope) -> Self
117    where
118        Self: Default,
119    {
120        Self::default().with_scope(scope)
121    }
122}
123
124/// Trait for combining the error types from two different passes
125/// into a single error.
126#[deprecated(
127    note = "`hugr-passes` is deprecated. Use tket::passes instead",
128    since = "0.26.2"
129)]
130pub trait ErrorCombiner<A, B>: Error {
131    /// Create a combined error from the first pass's error.
132    fn from_first(a: A) -> Self;
133    /// Create a combined error from the second pass's error.
134    fn from_second(b: B) -> Self;
135}
136
137impl<A: Error, B: Into<A>> ErrorCombiner<A, B> for A {
138    fn from_first(a: A) -> Self {
139        a
140    }
141
142    fn from_second(b: B) -> Self {
143        b.into()
144    }
145}
146
147impl<A: Error, B: Error> ErrorCombiner<A, B> for Either<A, B> {
148    fn from_first(a: A) -> Self {
149        Either::Left(a)
150    }
151
152    fn from_second(b: B) -> Self {
153        Either::Right(b)
154    }
155}
156
157// Note: in the short term we could wish for two more impls:
158//   impl<E:Error> ErrorCombiner<Infallible, E> for E
159//   impl<E:Error> ErrorCombiner<E, Infallible> for E
160// however, these aren't possible as they conflict with
161//   impl<A, B:Into<A>> ErrorCombiner<A,B> for A
162// when A=E=Infallible, boo :-(.
163// However this will become possible, indeed automatic, when Infallible is replaced
164// by ! (never_type) as (unlike Infallible) ! converts Into anything
165
166// ErrMapper ------------------------------
167struct ErrMapper<P, H, E, F>(P, F, PhantomData<(E, H)>);
168
169impl<H: HugrMut, P: ComposablePass<H>, E: Error, F: Fn(P::Error) -> E> ErrMapper<P, H, E, F> {
170    fn new(pass: P, err_fn: F) -> Self {
171        Self(pass, err_fn, PhantomData)
172    }
173}
174
175impl<P: ComposablePass<H>, H: HugrMut, E: Error, F: Fn(P::Error) -> E> ComposablePass<H>
176    for ErrMapper<P, H, E, F>
177{
178    type Error = E;
179    type Result = P::Result;
180
181    fn run(&self, hugr: &mut H) -> Result<P::Result, Self::Error> {
182        self.0.run(hugr).map_err(&self.1)
183    }
184}
185
186impl<P: ComposablePass<H>, H: HugrMut, E: Error, F: Fn(P::Error) -> E> WithScope
187    for ErrMapper<P, H, E, F>
188{
189    fn with_scope(self, scope: impl Into<PassScope>) -> Self {
190        Self(self.0.with_scope(scope), self.1, PhantomData)
191    }
192}
193
194// ValidatingPass ------------------------------
195
196/// Error from a [`ValidatingPass`]
197#[derive(thiserror::Error, Debug)]
198#[deprecated(
199    note = "`hugr-passes` is deprecated. Use tket::passes instead",
200    since = "0.26.2"
201)]
202pub enum ValidatePassError<N, E>
203where
204    N: HugrNode + 'static,
205{
206    /// Validation failed on the initial HUGR.
207    #[error("Failed to validate input HUGR: {err}\n{pretty_hugr}")]
208    Input {
209        /// The validation error that occurred.
210        #[source]
211        err: Box<ValidationError<N>>,
212        /// A pretty-printed representation of the HUGR that failed validation.
213        pretty_hugr: String,
214    },
215    /// Validation failed on the final HUGR.
216    #[error("Failed to validate output HUGR: {err}\n{pretty_hugr}")]
217    Output {
218        /// The validation error that occurred.
219        #[source]
220        err: Box<ValidationError<N>>,
221        /// A pretty-printed representation of the HUGR that failed validation.
222        pretty_hugr: String,
223    },
224    /// An error from the underlying pass.
225    #[error(transparent)]
226    Underlying(Box<E>),
227}
228
229impl<N: HugrNode, E> From<E> for ValidatePassError<N, E> {
230    fn from(err: E) -> Self {
231        Self::Underlying(Box::new(err))
232    }
233}
234
235/// Runs an underlying pass, but with validation of the Hugr
236/// both before and afterwards.
237#[deprecated(
238    note = "`hugr-passes` is deprecated. Use tket::passes instead",
239    since = "0.26.2"
240)]
241pub struct ValidatingPass<P, H>(P, PhantomData<H>);
242
243impl<P: ComposablePass<H>, H: HugrMut> ValidatingPass<P, H> {
244    /// Return a new [`ValidatingPass`] that wraps the given underlying pass.
245    pub fn new(underlying: P) -> Self {
246        Self(underlying, PhantomData)
247    }
248
249    fn validation_impl<E>(
250        &self,
251        hugr: &H,
252        mk_err: impl FnOnce(ValidationError<H::Node>, String) -> ValidatePassError<H::Node, E>,
253    ) -> Result<(), ValidatePassError<H::Node, E>> {
254        hugr.validate()
255            .map_err(|err| mk_err(err, hugr.mermaid_string()))
256    }
257}
258
259impl<P: ComposablePass<H>, H: HugrMut> ComposablePass<H> for ValidatingPass<P, H>
260where
261    H::Node: 'static,
262{
263    type Error = ValidatePassError<H::Node, P::Error>;
264    type Result = P::Result;
265
266    fn run(&self, hugr: &mut H) -> Result<P::Result, Self::Error> {
267        self.validation_impl(hugr, |err, pretty_hugr| ValidatePassError::Input {
268            err: Box::new(err),
269            pretty_hugr,
270        })?;
271        let res = self.0.run(hugr)?;
272        self.validation_impl(hugr, |err, pretty_hugr| ValidatePassError::Output {
273            err: Box::new(err),
274            pretty_hugr,
275        })?;
276        Ok(res)
277    }
278}
279
280impl<P: ComposablePass<H>, H: HugrMut> WithScope for ValidatingPass<P, H> {
281    fn with_scope(self, scope: impl Into<PassScope>) -> Self {
282        Self(self.0.with_scope(scope), self.1)
283    }
284}
285
286// IfThen ------------------------------
287/// [`ComposablePass`] that executes a first pass that returns a `bool`
288/// result; and then, if-and-only-if that first result was true,
289/// executes a second pass
290#[deprecated(
291    note = "`hugr-passes` is deprecated. Use tket::passes instead",
292    since = "0.26.2"
293)]
294pub struct IfThen<E, H, A, B>(A, B, PhantomData<(E, H)>);
295
296impl<
297    A: ComposablePass<H, Result = bool>,
298    B: ComposablePass<H>,
299    H: HugrMut,
300    E: ErrorCombiner<A::Error, B::Error>,
301> IfThen<E, H, A, B>
302{
303    /// Make a new instance given the [`ComposablePass`] to run first
304    /// and (maybe) second
305    pub fn new(fst: A, opt_snd: B) -> Self {
306        Self(fst, opt_snd, PhantomData)
307    }
308}
309
310impl<
311    A: ComposablePass<H, Result = bool>,
312    B: ComposablePass<H>,
313    H: HugrMut,
314    E: ErrorCombiner<A::Error, B::Error>,
315> ComposablePass<H> for IfThen<E, H, A, B>
316{
317    type Error = E;
318    type Result = Option<B::Result>;
319
320    fn run(&self, hugr: &mut H) -> Result<Self::Result, Self::Error> {
321        let res: bool = self.0.run(hugr).map_err(ErrorCombiner::from_first)?;
322        res.then(|| self.1.run(hugr).map_err(ErrorCombiner::from_second))
323            .transpose()
324    }
325}
326
327impl<E, H, A, B> WithScope for IfThen<E, H, A, B>
328where
329    A: WithScope,
330    B: WithScope,
331{
332    fn with_scope(self, scope: impl Into<PassScope>) -> Self {
333        let scope = scope.into();
334        Self(
335            self.0.with_scope(scope.clone()),
336            self.1.with_scope(scope),
337            PhantomData,
338        )
339    }
340}
341
342#[cfg(test)]
343pub(crate) mod test {
344    use hugr_core::ops::Value;
345    use hugr_core::ops::dataflow::IOTrait;
346    use itertools::{Either, Itertools};
347
348    use hugr_core::builder::{
349        Dataflow, DataflowHugr, DataflowSubContainer, FunctionBuilder, HugrBuilder, ModuleBuilder,
350    };
351    use hugr_core::extension::prelude::{ConstUsize, MakeTuple, UnpackTuple, bool_t, usize_t};
352    use hugr_core::hugr::hugrmut::HugrMut;
353    use hugr_core::ops::{DFG, Input, OpType, Output, handle::NodeHandle};
354    use hugr_core::std_extensions::arithmetic::int_types::INT_TYPES;
355    use hugr_core::types::{Signature, TypeRow};
356    use hugr_core::{Hugr, HugrView, IncomingPort, Node};
357
358    use crate::composable::WithScope;
359    use crate::const_fold::{ConstFoldError, ConstantFoldPass};
360    use crate::dead_code::DeadCodeElimError;
361    use crate::untuple::UntupleResult;
362    use crate::{DeadCodeElimPass, PassScope, ReplaceTypes, UntuplePass};
363
364    use super::{ComposablePass, IfThen, ValidatePassError, ValidatingPass};
365
366    pub(crate) fn run_validating<P: ComposablePass<H>, H: HugrMut>(
367        pass: P,
368        hugr: &mut H,
369    ) -> Result<P::Result, ValidatePassError<H::Node, P::Error>> {
370        ValidatingPass::new(pass).run(hugr)
371    }
372
373    #[test]
374    fn test_then() {
375        let mut mb = ModuleBuilder::new();
376        let id1 = mb
377            .define_function("id1", Signature::new_endo([usize_t()]))
378            .unwrap();
379        let inps = id1.input_wires();
380        let id1 = id1.finish_with_outputs(inps).unwrap();
381        let id2 = mb
382            .define_function("id2", Signature::new_endo([usize_t()]))
383            .unwrap();
384        let inps = id2.input_wires();
385        let id2 = id2.finish_with_outputs(inps).unwrap();
386        let hugr = mb.finish_hugr().unwrap();
387
388        let c_usz = Value::from(ConstUsize::new(2));
389        let not_a_node = Node::from(portgraph::NodeIndex::new(0xFFFF));
390        assert!(!hugr.contains_node(not_a_node));
391        let dce = DeadCodeElimPass::default().with_entry_points([not_a_node]);
392        let cfold = ConstantFoldPass::default().with_inputs(id2.node(), [(0, c_usz.clone())]);
393
394        cfold.run(&mut hugr.clone()).unwrap();
395
396        let dce_err = DeadCodeElimError::NodeNotFound(not_a_node);
397        let r: Result<_, Either<DeadCodeElimError, ConstFoldError>> =
398            dce.clone().then(cfold.clone()).run(&mut hugr.clone());
399        assert_eq!(r, Err(Either::Left(dce_err.clone())));
400
401        let r: Result<_, Either<_, _>> = cfold
402            .clone()
403            .with_inputs(id1.node(), [(0, c_usz)])
404            .then(dce.clone())
405            .run(&mut hugr.clone());
406        assert_eq!(r, Err(Either::Right(dce_err)));
407
408        // Avoid wrapping in Either by mapping both to same Error
409        let r = dce
410            .map_err(|e| match e {
411                DeadCodeElimError::NodeNotFound(node) => ConstFoldError::MissingEntryPoint { node },
412            })
413            .then(cfold.clone())
414            .run(&mut hugr.clone());
415        assert_eq!(
416            r,
417            Err(ConstFoldError::MissingEntryPoint { node: not_a_node })
418        );
419
420        // Or where second supports Into first
421        let v = ValidatingPass::new(cfold.clone());
422        let r: Result<_, ValidatePassError<Node, ConstFoldError>> =
423            v.then(cfold).run(&mut hugr.clone());
424        r.unwrap();
425    }
426
427    #[test]
428    fn test_validation() {
429        let mut h = Hugr::new_with_entrypoint(DFG {
430            signature: Signature::new([usize_t()], [bool_t()]),
431        })
432        .unwrap();
433        let inp = h.add_node_with_parent(h.entrypoint(), Input::new([usize_t()]));
434        let outp = h.add_node_with_parent(h.entrypoint(), Output::new([bool_t()]));
435        h.connect(inp, 0, outp, 0);
436        let backup = h.clone();
437        let err = backup.validate().unwrap_err();
438
439        let no_inputs: [(IncomingPort, _); 0] = [];
440        let cfold = ConstantFoldPass::default().with_inputs(backup.entrypoint(), no_inputs);
441        cfold.run(&mut h).unwrap();
442        assert_eq!(h, backup); // Did nothing
443
444        let r = ValidatingPass::new(cfold).run(&mut h);
445        assert!(matches!(r, Err(ValidatePassError::Input { err: e, .. }) if *e == err));
446    }
447
448    #[test]
449    fn test_if_then() {
450        let tr = TypeRow::from(vec![usize_t(); 2]);
451
452        let h = {
453            let sig = Signature::new_endo(tr.clone());
454            let mut fb = FunctionBuilder::new("tupuntup", sig).unwrap();
455            let [a, b] = fb.input_wires_arr();
456            let tup = fb
457                .add_dataflow_op(MakeTuple::new(tr.clone()), [a, b])
458                .unwrap();
459            let untup = fb
460                .add_dataflow_op(UnpackTuple::new(tr.clone()), tup.outputs())
461                .unwrap();
462            fb.finish_hugr_with_outputs(untup.outputs()).unwrap()
463        };
464
465        let untup = UntuplePass::default().with_scope(PassScope::EntrypointRecursive);
466        {
467            // Change usize_t to INT_TYPES[6], and if that did anything (it will!), then Untuple
468            let mut repl = ReplaceTypes::default();
469            let usize_custom_t = usize_t().as_extension().unwrap().clone();
470            repl.set_replace_type(usize_custom_t, INT_TYPES[6].clone());
471            let ifthen = IfThen::<Either<_, _>, _, _, _>::new(repl, untup.clone());
472
473            let mut h = h.clone();
474            let r = run_validating(ifthen, &mut h).unwrap();
475            assert_eq!(
476                r,
477                Some(UntupleResult {
478                    rewrites_applied: 1
479                })
480            );
481            let [tuple_in, tuple_out] = h.children(h.entrypoint()).collect_array().unwrap();
482            assert_eq!(h.output_neighbours(tuple_in).collect_vec(), [tuple_out; 2]);
483        }
484
485        // Change INT_TYPES[5] to INT_TYPES[6]; that won't do anything, so don't Untuple
486        let mut repl = ReplaceTypes::default();
487        let i32_custom_t = INT_TYPES[5].as_extension().unwrap().clone();
488        repl.set_replace_type(i32_custom_t, INT_TYPES[6].clone());
489        let ifthen = IfThen::<Either<_, _>, _, _, _>::new(repl, untup);
490        let mut h = h;
491        let r = run_validating(ifthen, &mut h).unwrap();
492        assert_eq!(r, None);
493        assert_eq!(h.children(h.entrypoint()).count(), 4);
494        let mktup = h
495            .output_neighbours(h.first_child(h.entrypoint()).unwrap())
496            .next()
497            .unwrap();
498        assert_eq!(h.get_optype(mktup), &OpType::from(MakeTuple::new(tr)));
499    }
500}