-
Notifications
You must be signed in to change notification settings - Fork 13
feat!: ComposablePass trait allowing sequencing and validation #1895
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 22 commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
f63c3d1
Basic composable passes: MapErr, (a,b) for sequence, Validating
acl-cqc f3a0cb1
Add validate_if_test, rename ComposablePass::E -> Err
acl-cqc cac9b14
default to cfg! extension_inference
acl-cqc d64c229
Make composable public, re-export only ComposablePass
acl-cqc fccb130
Update const_fold...add_entry_point still a TODO
acl-cqc c7b93ac
Remove add_entry_point....plan to revert later
acl-cqc fbd1304
Update remove_dead_funcs
acl-cqc 7f27dc3
BREAKING: rm monomorphize, rename monomorphize_ref -> monomorphize
acl-cqc fba2042
Update monomorphize
acl-cqc 0f09a49
Remove hugr-passes::validation
acl-cqc f7f3493
Merge remote-tracking branch 'origin/main' into acl/composable_pass
acl-cqc c6f446b
Do not validate extensions in tests :-(
acl-cqc dab7039
MonomorphizePass: don't derive Default; tests use monomorphize restor…
acl-cqc 6f4f1b7
Merge 'origin/main' into acl/composable_pass
acl-cqc 7532b13
Test sequencing, deriving Clone+PartialEq for ConstFoldError
acl-cqc 5b94749
And test validation
acl-cqc 2487143
Driveby: no need for ConstFoldContext to implement Deref - from earli…
acl-cqc 395b8c3
redundant missing docs
acl-cqc c3e2e17
a few tweaks
acl-cqc c8653d0
fmt
acl-cqc bbbcd6f
sequence -> then, add note about Infallible
acl-cqc 80cedfc
rename Err -> Error
acl-cqc 55a1f18
Merge remote-tracking branch 'origin/main' into acl/composable_pass
acl-cqc 917b41f
Fix UntuplePass and ReplaceTypes (making CompasablePass) - adding Result
acl-cqc 7254443
WIP IfTrueThen
acl-cqc b00a272
Combine then+then_either via trait ErrorCombiner, applies to IfTrueTh…
acl-cqc 29f8095
Reorder, separate with ----- comments, some missing docs
acl-cqc 1a9d28b
Merge remote-tracking branch 'origin/main' into acl/composable_pass
acl-cqc 9d4b478
test_sequence => test_then, both orders
acl-cqc 58b7326
IfTrueThen=>IfThen, test; derive PartialEq for UntupleResult
acl-cqc 8a1e4c5
imports wtf git
acl-cqc 5daab98
Fix all-features
acl-cqc 4bbe0a6
Merge remote-tracking branch 'origin/main' into acl/composable_pass
acl-cqc 0fc3ed6
Merge remote-tracking branch 'origin/release-rs-v0.16.0' into acl/com…
acl-cqc 75fc36a
tidy imports, correct comment
acl-cqc b7b9569
inline change_type_then_untup
acl-cqc c543be3
clippy
acl-cqc 662d765
Fix all-features by disabling extension validation
acl-cqc a597008
comment typo
acl-cqc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| //! Compiler passes and utilities for composing them | ||
|
|
||
| use std::{error::Error, marker::PhantomData}; | ||
|
|
||
| use hugr_core::hugr::{hugrmut::HugrMut, ValidationError}; | ||
| use hugr_core::HugrView; | ||
| use itertools::Either; | ||
|
|
||
| /// An optimization pass that can be sequenced with another and/or wrapped | ||
| /// e.g. by [ValidatingPass] | ||
| pub trait ComposablePass: Sized { | ||
| type Error: Error; | ||
| fn run(&self, hugr: &mut impl HugrMut) -> Result<(), Self::Error>; | ||
|
|
||
| fn map_err<E2: Error>(self, f: impl Fn(Self::Error) -> E2) -> impl ComposablePass<Error = E2> { | ||
| ErrMapper::new(self, f) | ||
| } | ||
|
|
||
| /// Returns a [ComposablePass] that does "`self` then `other`", so long as | ||
| /// `other::Err` maps into ours. | ||
| fn then<P: ComposablePass>(self, other: P) -> impl ComposablePass<Error = Self::Error> | ||
| where | ||
| P::Error: Into<Self::Error>, | ||
| { | ||
| (self, other.map_err(Into::into)) | ||
| } | ||
|
|
||
| /// Returns a [ComposablePass] that does "`self` then `other`", combining | ||
| /// the two error types via `Either` | ||
| fn then_either<P: ComposablePass>( | ||
| self, | ||
| other: P, | ||
| ) -> impl ComposablePass<Error = Either<Self::Error, P::Error>> { | ||
| (self.map_err(Either::Left), other.map_err(Either::Right)) | ||
| } | ||
|
|
||
| // Note: in the short term another variant could be useful: | ||
| // fn then_inf(self, other: impl ComposablePass<Err=Infallible>) -> impl ComposablePass<Err = Self::Err> | ||
| // however this will become redundant when Infallible is replaced by ! (never_type) | ||
| // as (unlike Infallible) ! converts Into anything | ||
| } | ||
|
|
||
| struct ErrMapper<P, E, F>(P, F, PhantomData<E>); | ||
|
|
||
| impl<P: ComposablePass, E: Error, F: Fn(P::Error) -> E> ErrMapper<P, E, F> { | ||
| fn new(pass: P, err_fn: F) -> Self { | ||
| Self(pass, err_fn, PhantomData) | ||
| } | ||
| } | ||
|
|
||
| impl<P: ComposablePass, E: Error, F: Fn(P::Error) -> E> ComposablePass for ErrMapper<P, E, F> { | ||
| type Error = E; | ||
|
|
||
| fn run(&self, hugr: &mut impl HugrMut) -> Result<(), Self::Error> { | ||
| self.0.run(hugr).map_err(&self.1) | ||
| } | ||
| } | ||
|
|
||
| impl<E: Error, P1: ComposablePass<Error = E>, P2: ComposablePass<Error = E>> ComposablePass | ||
| for (P1, P2) | ||
| { | ||
| type Error = E; | ||
|
|
||
| fn run(&self, hugr: &mut impl HugrMut) -> Result<(), Self::Error> { | ||
| self.0.run(hugr)?; | ||
| self.1.run(hugr) | ||
| } | ||
| } | ||
|
|
||
| /// Error from a [ValidatingPass] | ||
| #[derive(thiserror::Error, Debug)] | ||
| pub enum ValidatePassError<E> { | ||
| #[error("Failed to validate input HUGR: {err}\n{pretty_hugr}")] | ||
| Input { | ||
| #[source] | ||
| err: ValidationError, | ||
| pretty_hugr: String, | ||
| }, | ||
| #[error("Failed to validate output HUGR: {err}\n{pretty_hugr}")] | ||
| Output { | ||
| #[source] | ||
| err: ValidationError, | ||
| pretty_hugr: String, | ||
| }, | ||
| #[error(transparent)] | ||
| Underlying(#[from] E), | ||
| } | ||
|
|
||
| /// Runs an underlying pass, but with validation of the Hugr | ||
| /// both before and afterwards. | ||
| pub struct ValidatingPass<P>(P, bool); | ||
|
|
||
| impl<P: ComposablePass> ValidatingPass<P> { | ||
| pub fn new_default(underlying: P) -> Self { | ||
| // Self(underlying, cfg!(feature = "extension_inference")) | ||
| // Sadly, many tests fail with extension inference, hence: | ||
| Self(underlying, false) | ||
| } | ||
|
|
||
| pub fn new_validating_extensions(underlying: P) -> Self { | ||
| Self(underlying, true) | ||
| } | ||
|
|
||
| pub fn new(underlying: P, validate_extensions: bool) -> Self { | ||
| Self(underlying, validate_extensions) | ||
| } | ||
|
|
||
| fn validation_impl<E>( | ||
| &self, | ||
| hugr: &impl HugrView, | ||
| mk_err: impl FnOnce(ValidationError, String) -> ValidatePassError<E>, | ||
| ) -> Result<(), ValidatePassError<E>> { | ||
| match self.1 { | ||
| false => hugr.validate_no_extensions(), | ||
| true => hugr.validate(), | ||
| } | ||
| .map_err(|err| mk_err(err, hugr.mermaid_string())) | ||
| } | ||
| } | ||
|
|
||
| impl<P: ComposablePass> ComposablePass for ValidatingPass<P> { | ||
| type Error = ValidatePassError<P::Error>; | ||
|
|
||
| fn run(&self, hugr: &mut impl HugrMut) -> Result<(), Self::Error> { | ||
| self.validation_impl(hugr, |err, pretty_hugr| ValidatePassError::Input { | ||
| err, | ||
| pretty_hugr, | ||
| })?; | ||
| self.0.run(hugr).map_err(ValidatePassError::Underlying)?; | ||
| self.validation_impl(hugr, |err, pretty_hugr| ValidatePassError::Output { | ||
| err, | ||
| pretty_hugr, | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| pub(crate) fn validate_if_test<P: ComposablePass>( | ||
cqc-alec marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| pass: P, | ||
| hugr: &mut impl HugrMut, | ||
| ) -> Result<(), ValidatePassError<P::Error>> { | ||
| if cfg!(test) { | ||
| ValidatingPass::new_default(pass).run(hugr) | ||
| } else { | ||
| pass.run(hugr).map_err(ValidatePassError::Underlying) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod test { | ||
| use std::convert::Infallible; | ||
|
|
||
| use hugr_core::builder::{ | ||
| Container, Dataflow, DataflowSubContainer, HugrBuilder, ModuleBuilder, | ||
| }; | ||
| use hugr_core::extension::prelude::{bool_t, usize_t, ConstUsize}; | ||
| use hugr_core::hugr::hugrmut::HugrMut; | ||
| use hugr_core::ops::{handle::NodeHandle, Input, Output, DEFAULT_OPTYPE, DFG}; | ||
| use hugr_core::{types::Signature, Hugr, HugrView, IncomingPort}; | ||
| use itertools::Either; | ||
|
|
||
| use crate::composable::{ValidatePassError, ValidatingPass}; | ||
| use crate::const_fold::{ConstFoldError, ConstantFoldPass}; | ||
| use crate::DeadCodeElimPass; | ||
|
|
||
| use super::ComposablePass; | ||
|
|
||
| #[test] | ||
| fn test_sequence() { | ||
| let mut mb = ModuleBuilder::new(); | ||
| let id1 = mb | ||
| .define_function("id1", Signature::new_endo(usize_t())) | ||
| .unwrap(); | ||
| let inps = id1.input_wires(); | ||
| let id1 = id1.finish_with_outputs(inps).unwrap(); | ||
| let id2 = mb | ||
| .define_function("id2", Signature::new_endo(usize_t())) | ||
| .unwrap(); | ||
| let inps = id2.input_wires(); | ||
| let id2 = id2.finish_with_outputs(inps).unwrap(); | ||
| let hugr = mb.finish_hugr().unwrap(); | ||
|
|
||
| let dce = DeadCodeElimPass::default().with_entry_points([id1.node()]); | ||
| let cfold = | ||
| ConstantFoldPass::default().with_inputs(id2.node(), [(0, ConstUsize::new(2).into())]); | ||
|
|
||
| cfold.run(&mut hugr.clone()).unwrap(); | ||
|
|
||
| let exp_err = ConstFoldError::InvalidEntryPoint(id2.node(), DEFAULT_OPTYPE); | ||
| let r: Result<(), Either<Infallible, ConstFoldError>> = dce | ||
| .clone() | ||
| .then_either(cfold.clone()) | ||
| .run(&mut hugr.clone()); | ||
| assert_eq!(r, Err(Either::Right(exp_err.clone()))); | ||
|
|
||
| let r: Result<(), ConstFoldError> = dce | ||
| .map_err(|inf| match inf {}) | ||
| .then(cfold) | ||
| .run(&mut hugr.clone()); | ||
| assert_eq!(r, Err(exp_err)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_validation() { | ||
| let mut h = Hugr::new(DFG { | ||
| signature: Signature::new(usize_t(), bool_t()), | ||
| }); | ||
| let inp = h.add_node_with_parent( | ||
| h.root(), | ||
| Input { | ||
| types: usize_t().into(), | ||
| }, | ||
| ); | ||
| let outp = h.add_node_with_parent( | ||
| h.root(), | ||
| Output { | ||
| types: bool_t().into(), | ||
| }, | ||
| ); | ||
| h.connect(inp, 0, outp, 0); | ||
| let backup = h.clone(); | ||
| let err = backup.validate().unwrap_err(); | ||
|
|
||
| let no_inputs: [(IncomingPort, _); 0] = []; | ||
| let cfold = ConstantFoldPass::default().with_inputs(backup.root(), no_inputs); | ||
| cfold.run(&mut h).unwrap(); | ||
| assert_eq!(h, backup); // Did nothing | ||
|
|
||
| let r = ValidatingPass(cfold, false).run(&mut h); | ||
| assert!(matches!(r, Err(ValidatePassError::Input { err: e, .. }) if e == err)); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.