-
Notifications
You must be signed in to change notification settings - Fork 14
feat: LocalizeEdges pass #2237
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
feat: LocalizeEdges pass #2237
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding some comments, I still need to finish localize.rs
hugr-passes/src/non_local.rs
Outdated
} | ||
} | ||
|
||
/// Returns an iterator over all non local edges in a Hugr. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
drive-by: This is no longer "all nl edges", but the ones that interact with the entrypoint descendants.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. Or rather, it's ones that are incoming to the subtree. So actually only one endpoint need be in the subtree.
So my thinking here was that there were two reasonable policies:
- All nonlocal edges within the Hugr, ignoring
hugr.entrypoint()
. - All nonlocal edges entirely beneath the entrypoint.
I meant to go for the latter, on the grounds that you can then get the former behaviour by passing entrypoint==root. Clearly I missed the latter ;-), I think the sensible thing is to put in that check.
However, if you think it'd be better to do this in stages (e.g. have two methods _root
and _entrypoint
, make the unsuffixed version delegate to _root
but @deprecate
it, and maybe eventually rename the _entrypoint
variant back to no-suffix) then happy to do that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed that "All nonlocal edges entirely beneath the entrypoint" is the way to go.
I think most passes should avoid out-of-tree modifications unless explicitly stated (e.g. defining a function in the module).
This should be compatible with the pre-entrypoint API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed that "All nonlocal edges entirely beneath the entrypoint" is the way to go.
I think most passes should avoid out-of-tree modifications unless explicitly stated (e.g. defining a function in the module).
This should be compatible with the pre-entrypoint API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, filtered to beneath the entrypoint.
hugr.linked_outputs(node, in_p) | ||
.any(|(neighbour_node, _)| parent != hugr.get_parent(neighbour_node)) | ||
.then_some((node, in_p)) | ||
let (src, _) = hugr.single_linked_output(node, in_p)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are also now missing edges that connect out of the entrypoint descendants. (I realize it's annoying to check and avoid duplicates :/)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Argh. Yes.
So my thinking was that for transformation, we should mutate the Hugr only beneath the entrypoint, in which case we only want edges with both ends beneath the entrypoint.
Counterexamples?
- An edge entering the subtree from a sibling of the entrypoint. This requires retargetting that edge to the entrypoint, and mutating the entrypoint node. Since no node outside the subtree is changed, I guess you could argue this is allowed, although I don't think I would myself....(looks to me like you've changed an edge outside the subtree!)
- Unless the entrypoint is a DataflowBlock, the only possible NL edge leaving it would be an
Ext
edge from the entrypoint itself (to a descendant of a sibling). I think that edge is outside the subtree....(indeed, localizing it does not even mutate the entrypoint node) - If the entrypoint is a Dataflow Block, then
Dom
edges could leave its children to (descendants of) other DFB siblings-of-the-entrypoint, i.e. outside the tree. But clearly localizing those is gonna involve widespread changes outside the subtree (as well as to the entrypoint node itself).
hugr-passes/src/non_local.rs
Outdated
// Group all the non-local edges in the graph by target node, | ||
// storing for each the source and type (well-defined as these are Value edges). | ||
let nonlocal_edges_map: HashMap<_, _> = nonlocal_edges(hugr) | ||
.filter_map(|(node, inport)| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this a filter_map
instead of a map
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good spot, we've already skipped over inports without a single_linked_output
in nonlocal_edges(hugr)
. Thanks!
hugr-passes/src/non_local.rs
Outdated
else { | ||
panic!("impossible") | ||
}; | ||
Some((node, (source, ty))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we have multiple nl edges going to the same node?
This will drop all but one while collect
ing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh good point. ("All compliments accrue to Doug but all shortcomings are my fault" 😉 😉 ). Yes, we dedup sources in the ExtraSourceReqs map (which is correct) but should not do so here. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will add a test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
localize_dfg
test extended
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, should be mergeable once the entrypoint-descendants filter is updated.
.then_some((node, in_p)) | ||
let (src, _) = hugr.single_linked_output(node, in_p)?; | ||
(hugr.get_parent(node) != hugr.get_parent(src) | ||
&& ancestors(src, hugr).any(|a| a == hugr.entrypoint())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to use HugrInternals::region_portgraph
instead of this ancestors
check here but got a bit stuck - it looks like you should use the nodemap to convert Nodes to portgraph NodeIndex
es to look up in the portgraph Region
, but the nodemap says it is undefined for Node
s outside the region, which prevents using it to check if Node
s are in the region! So I can come back to this if I'm missing something (@aborgna-q ?) but otherwise I'm just gonna accept an O(depth) =~ O(log(n)) cost multiplier for now. (We could optimize by caching entry_descendants in a HashSet if we really wanted.)
## 🤖 New release * `hugr-model`: 0.20.0 -> 0.20.1 * `hugr-core`: 0.20.0 -> 0.20.1 (✓ API compatible changes) * `hugr-llvm`: 0.20.0 -> 0.20.1 (✓ API compatible changes) * `hugr-passes`: 0.20.0 -> 0.20.1 (✓ API compatible changes) * `hugr`: 0.20.0 -> 0.20.1 (✓ API compatible changes) * `hugr-cli`: 0.20.0 -> 0.20.1 (✓ API compatible changes) <details><summary><i><b>Changelog</b></i></summary><p> ## `hugr-model` <blockquote> ## [0.20.0](hugr-model-v0.19.0...hugr-model-v0.20.0) - 2025-05-14 ### New Features - [**breaking**] Mark all Error enums as non_exhaustive ([#2056](#2056)) - [**breaking**] Bump MSRV to 1.85 ([#2136](#2136)) - Export and import entrypoints via metadata in `hugr-model`. ([#2172](#2172)) - Define text-model envelope formats ([#2188](#2188)) - Import CFG regions without adding an entry block. ([#2200](#2200)) - Symbol applications can leave out prefixes of wildcards. ([#2201](#2201)) </blockquote> ## `hugr-core` <blockquote> ## [0.20.1](hugr-core-v0.20.0...hugr-core-v0.20.1) - 2025-06-03 ### Bug Fixes - check well-definedness of DFG wires in validate ([#2221](#2221)) - Check for order edges in SiblingSubgraph::from_node ([#2223](#2223)) - Make SumType::Unit(N) equal to SumType::General([(); N]) ([#2250](#2250)) - canonicalize_nodes sometimes mangles the entrypoint ([#2263](#2263)) ### New Features - Add PersistentHugr ([#2080](#2080)) - Add `Type::used_extensions` ([#2224](#2224)) - Add boundary edge traversal in SimpleReplacement ([#2231](#2231)) - Add signature map function for DFGs ([#2239](#2239)) - PersistentHugr implements HugrView ([#2202](#2202)) - PersistentHugr Walker API ([#2168](#2168)) - Hugr::store_with_exts and auto-include in serde_as ([#2280](#2280)) ### Refactor - tidies/readability improvements to PersistentHugr ([#2251](#2251)) - Deprecate ValidationError::ExtensionError ([#2281](#2281)) ### Testing - Ignore miri errors in tests involving `assert_snapshot` ([#2261](#2261)) </blockquote> ## `hugr-llvm` <blockquote> ## [0.20.1](hugr-llvm-v0.20.0...hugr-llvm-v0.20.1) - 2025-06-03 ### Bug Fixes - Make SumType::Unit(N) equal to SumType::General([(); N]) ([#2250](#2250)) ### Testing - Add exec tests for widen op ([#2043](#2043)) </blockquote> ## `hugr-passes` <blockquote> ## [0.20.1](hugr-passes-v0.20.0...hugr-passes-v0.20.1) - 2025-06-03 ### Bug Fixes - Dataflow analysis produces unsound results on Hugrs with entrypoint ([#2255](#2255)) ### New Features - LocalizeEdges pass ([#2237](#2237)) </blockquote> ## `hugr` <blockquote> ## [0.20.1](hugr-v0.20.0...hugr-v0.20.1) - 2025-06-03 ### Bug Fixes - Dataflow analysis produces unsound results on Hugrs with entrypoint ([#2255](#2255)) - check well-definedness of DFG wires in validate ([#2221](#2221)) - Check for order edges in SiblingSubgraph::from_node ([#2223](#2223)) - Make SumType::Unit(N) equal to SumType::General([(); N]) ([#2250](#2250)) - canonicalize_nodes sometimes mangles the entrypoint ([#2263](#2263)) ### New Features - LocalizeEdges pass ([#2237](#2237)) - Add PersistentHugr ([#2080](#2080)) - Add `Type::used_extensions` ([#2224](#2224)) - Add boundary edge traversal in SimpleReplacement ([#2231](#2231)) - Add signature map function for DFGs ([#2239](#2239)) - PersistentHugr implements HugrView ([#2202](#2202)) - PersistentHugr Walker API ([#2168](#2168)) - Hugr::store_with_exts and auto-include in serde_as ([#2280](#2280)) ### Refactor - tidies/readability improvements to PersistentHugr ([#2251](#2251)) - Deprecate ValidationError::ExtensionError ([#2281](#2281)) ### Testing - Ignore miri errors in tests involving `assert_snapshot` ([#2261](#2261)) </blockquote> ## `hugr-cli` <blockquote> ## [0.20.1](hugr-cli-v0.20.0...hugr-cli-v0.20.1) - 2025-06-03 ### New Features - support external subcommands via PATH ([#1343](#1343)) ([#2278](#2278)) </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/).
closes #1234
Gathers all nonlocal edges, then transforms from "outside in" to maintain invariant that there are no edges incoming at the level being processed.