-
Notifications
You must be signed in to change notification settings - Fork 7
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
Add SiblingMut allowing modification of a SiblingGraph #522
Conversation
…th valid_node checks ==> SiblingGraph::new's "get_optype" will implicitly check the root is in the base view. This does mean some unnecessary index checking for &Hugr's. We could continue to reimplement those methods for AsRef<Hugr> if we want to avoid this inefficiency. Alternatively we could make UnmanagedDenseMap return Options rather than unwrapping. Some other ugly alternative/hacks for returning default values - DEFAULT_{NODE,OP}TYPE
054bac3
to
b4b4e5e
Compare
9f0af34
to
55f2510
Compare
@@ -37,8 +37,8 @@ pub trait HugrMut: HugrView + HugrMutInternals { | |||
parent: Node, | |||
op: impl Into<OpType>, | |||
) -> Result<Node, HugrError> { | |||
self.valid_node(parent)?; | |||
self.hugr_mut().add_op_with_parent(parent, op) | |||
// TODO: Default to `NodeType::open_extensions` once we can infer extensions |
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.
(This comment just moved from the other add_op_with_parent
)
}; | ||
|
||
// 4(b). Reconnect exit edge to the new exit node within the inner CFG | ||
let mut in_bb_view: SiblingMut<'_, BasicBlockID> = |
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 guess maybe I should comment why we're building these SiblingMut's (i.e. in case the HugrMut we're operating on is a SiblingMut itself), as they would not be necessary if the h: impl HugrMut
was in fact a &mut 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.
Nice!
src/hugr/views/sibling.rs
Outdated
|
||
fn linked_ports(&self, node: Node, port: Port) -> Self::PortLinks<'_> { | ||
match self.contains_node(node) { | ||
true => self.base_hugr().linked_ports(node, port), |
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.
This won't filter external edges.
src/hugr/views/sibling.rs
Outdated
|
||
fn node_connections(&self, node: Node, other: Node) -> Self::NodeConnections<'_> { | ||
match self.contains_node(node) && self.contains_node(other) { | ||
true => self.base_hugr().node_connections(node, other), |
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.
Same here, this isn't filtering the nodes.
src/hugr/views/sibling.rs
Outdated
self.valid_node(node).unwrap(); // ?? Or return empty iterator? | ||
self.base_hugr().neighbours(node, dir) // Or self.hugr ? | ||
} | ||
|
||
fn all_neighbours(&self, node: Node) -> Self::Neighbours<'_> { | ||
self.valid_node(node).unwrap(); // ?? Or return empty iterator? | ||
self.base_hugr().all_neighbours(node) |
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'd say return an empty one.
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.
Ooops, missed those when I fixed a bunch of other unwrap
s. Done, 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.
I wonder if delegate!
ing all the HugrView
impl to a SiblingGraph
is feasible. In theory it should be free after all the layers are optimized away...
(there is no caching nor anything on a flat filtered graph, we only do that for descendants).
Have you tried 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.
Not seriously. The only two strategies I had thought about before, are easily dismissed:
- Keep a SiblingGraph permanently in the SiblingMut. But that means there's an immutable borrow of the underlying Hugr sitting around, which means we can't mutate it, which is pretty useless.
- Construct a SiblingGraph on demand, call the method, then discard it. This is plausible for things like 'get_parent
but falls down for any of the iterators - we need (e.g.)
SiblingMut.nodesto return the
nodes()of the SiblingGraph, and the nodes refers to the SiblingGraph, which is a local variable deallocated as soon as
SiblingMut.nodes` returns. (Returning a reference to temporary variable). So that doesn't fly.
However, ok, thinking a bit more....
- what I'd like to do is declare a
struct SGHolder<T>
which holds (owns) a SiblingGraph, and a T which can refer to that SiblingGraph; and thenimpl...Iterator for SGHolder<T> where T: Iterator
. However, it doesn't seem there's any way to have a struct like that - e.g. here or many others. - I think the only option remaining is to change the
nodes()
method ofHugrView
itself to return not aSelf::Nodes<'_>
but aSelf::Nodes<'t>
for some lifetime't
longer thanSelf
- this might have to be a parameter of thenodes()
method and we might need a max constraint from the other direction as part of HugrView (i.e.trait HugrView<'g> { .... fn<'t> nodes(&self) -> Self::Nodes<'t> where 't: '_, 'g: 't
- I think). Thennodes()
can return an iterator that references the Hugr inside the view, but not the view itself. However this may lead to other problems... (shout if you want but otherwise I'll see where I get to.)
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.
No, the latter would work if SiblingGraph
methods were returning things that referenced the underlying Hugr
, but not the portgraph, as that's part of SiblingGraph (and gets deallocated when the SiblingMut methods return)...
So we'd have to store a portgraph in the SiblingMut? I.e. keep it up-to-date following each mutation (unless we change HugrView methods to take &mut self
). I don't think we want to do that, but thoughts?
I also see suggestions that we change other classes (=~= the various iterators) to use Arc
/Rc
s rather than &
s, but given we want to prevent mutation of the SiblingMut while the iterators are live, I don't see how that works.
I'll look at the various refs-between-fields-of-a-struct crates linked above, tho I think these are basically wrappers over unsafe
....
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, ouroboros
allows the intra-field-refs OK, but still seems to struggle with our fancy lifetime-parameterized trait members (like type Nodes<'a>
) - see branch sibling-mut/ouroboros
(commit 95cfb0d, failing with a "lifetime doesn't live long enough" error - seems to me like '1 and '2 in that error should both be '_ i.e. the lifetime of the enclosing SiblingMut??). Starting to feel like I'm out of ideas here...
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.
So I tried parameterizing the HugrView and using Ouroboros there, to add a nodes2()
that returns an iterator outliving the view itself...but still got stuck. I think the fundamental issue is that with Ouroboros, you define a struct holding a Foo and a Bar that "borrows" the Foo; but when you create the struct, you provide a function to create the Bar given an &Foo
but the types we are using require the Bar returned by that function to outlive the &Foo
reference (passed to the function). Possibly we could get around this by going right the way into portgraph and adding ouroboros-type magic there, but I don't think I'm going there.
Fundamentally, we want some way to convert, say, a portgraph::Children<'a>
into a Children<'static>
, i.e. copying the extra data.
But I think there is a much easier way to do that - copy the iterated items, rather than the data over which the iterator runs. I'll give that a go here....
Ok, so
|
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.
Yeah, this is a non-trivial problem :P
This look alright as a first implementation. I say open a P-low
perf
issue to investigate the performance here.
src/hugr/views/sibling.rs
Outdated
@@ -41,24 +43,63 @@ pub struct SiblingGraph<'g, Root = Node> { | |||
_phantom: std::marker::PhantomData<Root>, | |||
} | |||
|
|||
/// HugrView trait members common to both [SiblingGraph] and [SiblingMut], | |||
/// i.e. that rely only on [HugrInternals::base_hugr] | |||
macro_rules! base_members { |
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.
Uhm, this is iffy but I guess it's ok.
I'd just rename it to impl_base_members
to convey a bit more info at the callsites.
HugrView::get_io
methods - could pull that out into a preliminary PR, it's just tidying a wrapper functionHugrMut::add_op_with_parent
is just a wrapper aroundadd_node_with_parent
so make that a default-impl tooset_parent
)