Skip to content

Commit e31d52b

Browse files
committed
iter: introduce right-to-left post-order iterator, use throughout codebase
We have several algorithms throughout the codebase which "translate" a recursive object by running a post-order iterator over it, building a modified copy node-by-node. We frequently do this by iterating over an Arc structure, pushing each created node onto a stack, and then using the `child_indices` member of the `PostOrderIterItem` struct to index into the stack. We copy elements out of the stack using Arc::clone and then push a new element. The result is that for an object with N nodes, we construct a stack with N elements, call Arc::clone N-1 times, and often we need to bend over backward to turn &self into an Arc<Self> before starting. There is a much more efficient way: with a post-order iterator, each node appears directly after its children. So we can just pop children off of the stack, construct the new node, and push that onto the stack. As long as we always pop all of the children, our stack will never grow beyond the depth of the object in question, and we can avoid some Arc::clones. Using a right-to-left iterator means that we can call .pop() in the "natural" way rather than having to muck about reordering the children. This commit converts every single use of post_order_iter in the library to use this new algorithm. In the case of Miniscript::substitute_raw_pkh, the old algorithm was actually completely wrong. The next commit adds a unit test.
1 parent 5da250a commit e31d52b

File tree

4 files changed

+200
-111
lines changed

4 files changed

+200
-111
lines changed

src/iter/tree.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ pub trait TreeLike: Clone + Sized {
104104
fn post_order_iter(self) -> PostOrderIter<Self> {
105105
PostOrderIter { index: 0, stack: vec![IterStackItem::unprocessed(self, None)] }
106106
}
107+
108+
/// Obtains an iterator of all the nodes rooted at the DAG, in right-to-left post order.
109+
///
110+
/// This ordering is useful for "translation" algorithms which iterate over a
111+
/// structure, pushing translated nodes and popping children.
112+
fn rtl_post_order_iter(self) -> RtlPostOrderIter<Self> {
113+
RtlPostOrderIter { inner: Rtl(self).post_order_iter() }
114+
}
107115
}
108116

109117
/// Element stored internally on the stack of a [`PostOrderIter`].
@@ -202,6 +210,53 @@ impl<T: TreeLike> Iterator for PostOrderIter<T> {
202210
}
203211
}
204212

213+
/// Adaptor structure to allow iterating in right-to-left order.
214+
#[derive(Clone, Debug)]
215+
struct Rtl<T>(pub T);
216+
217+
impl<T: TreeLike> TreeLike for Rtl<T> {
218+
type NaryChildren = T::NaryChildren;
219+
220+
fn nary_len(tc: &Self::NaryChildren) -> usize { T::nary_len(tc) }
221+
fn nary_index(tc: Self::NaryChildren, idx: usize) -> Self {
222+
let rtl_idx = T::nary_len(&tc) - idx - 1;
223+
Rtl(T::nary_index(tc, rtl_idx))
224+
}
225+
226+
fn as_node(&self) -> Tree<Self, Self::NaryChildren> {
227+
match self.0.as_node() {
228+
Tree::Nullary => Tree::Nullary,
229+
Tree::Unary(a) => Tree::Unary(Rtl(a)),
230+
Tree::Binary(a, b) => Tree::Binary(Rtl(b), Rtl(a)),
231+
Tree::Ternary(a, b, c) => Tree::Ternary(Rtl(c), Rtl(b), Rtl(a)),
232+
Tree::Nary(data) => Tree::Nary(data),
233+
}
234+
}
235+
}
236+
237+
/// Iterates over a DAG in _right-to-left post order_.
238+
///
239+
/// That means nodes are yielded in the order (right child, left child, parent).
240+
#[derive(Clone, Debug)]
241+
pub struct RtlPostOrderIter<T> {
242+
inner: PostOrderIter<Rtl<T>>,
243+
}
244+
245+
impl<T: TreeLike> Iterator for RtlPostOrderIter<T> {
246+
type Item = PostOrderIterItem<T>;
247+
248+
fn next(&mut self) -> Option<Self::Item> {
249+
self.inner.next().map(|mut item| {
250+
item.child_indices.reverse();
251+
PostOrderIterItem {
252+
child_indices: item.child_indices,
253+
index: item.index,
254+
node: item.node.0,
255+
}
256+
})
257+
}
258+
}
259+
205260
/// Iterates over a [`TreeLike`] in _pre order_.
206261
///
207262
/// Unlike the post-order iterator, this one does not keep track of indices

src/miniscript/mod.rs

Lines changed: 107 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,7 @@ mod private {
8686
/// and they can call `Miniscript::clone`.
8787
fn clone(&self) -> Self {
8888
let mut stack = vec![];
89-
for item in self.post_order_iter() {
90-
let child_n = |n| Arc::clone(&stack[item.child_indices[n]]);
91-
89+
for item in self.rtl_post_order_iter() {
9290
let new_term = match item.node.node {
9391
Terminal::PkK(ref p) => Terminal::PkK(p.clone()),
9492
Terminal::PkH(ref p) => Terminal::PkH(p.clone()),
@@ -101,23 +99,31 @@ mod private {
10199
Terminal::Hash160(ref x) => Terminal::Hash160(x.clone()),
102100
Terminal::True => Terminal::True,
103101
Terminal::False => Terminal::False,
104-
Terminal::Alt(..) => Terminal::Alt(child_n(0)),
105-
Terminal::Swap(..) => Terminal::Swap(child_n(0)),
106-
Terminal::Check(..) => Terminal::Check(child_n(0)),
107-
Terminal::DupIf(..) => Terminal::DupIf(child_n(0)),
108-
Terminal::Verify(..) => Terminal::Verify(child_n(0)),
109-
Terminal::NonZero(..) => Terminal::NonZero(child_n(0)),
110-
Terminal::ZeroNotEqual(..) => Terminal::ZeroNotEqual(child_n(0)),
111-
Terminal::AndV(..) => Terminal::AndV(child_n(0), child_n(1)),
112-
Terminal::AndB(..) => Terminal::AndB(child_n(0), child_n(1)),
113-
Terminal::AndOr(..) => Terminal::AndOr(child_n(0), child_n(1), child_n(2)),
114-
Terminal::OrB(..) => Terminal::OrB(child_n(0), child_n(1)),
115-
Terminal::OrD(..) => Terminal::OrD(child_n(0), child_n(1)),
116-
Terminal::OrC(..) => Terminal::OrC(child_n(0), child_n(1)),
117-
Terminal::OrI(..) => Terminal::OrI(child_n(0), child_n(1)),
118-
Terminal::Thresh(ref thresh) => Terminal::Thresh(
119-
thresh.map_from_post_order_iter(&item.child_indices, &stack),
102+
Terminal::Alt(..) => Terminal::Alt(stack.pop().unwrap()),
103+
Terminal::Swap(..) => Terminal::Swap(stack.pop().unwrap()),
104+
Terminal::Check(..) => Terminal::Check(stack.pop().unwrap()),
105+
Terminal::DupIf(..) => Terminal::DupIf(stack.pop().unwrap()),
106+
Terminal::Verify(..) => Terminal::Verify(stack.pop().unwrap()),
107+
Terminal::NonZero(..) => Terminal::NonZero(stack.pop().unwrap()),
108+
Terminal::ZeroNotEqual(..) => Terminal::ZeroNotEqual(stack.pop().unwrap()),
109+
Terminal::AndV(..) => {
110+
Terminal::AndV(stack.pop().unwrap(), stack.pop().unwrap())
111+
}
112+
Terminal::AndB(..) => {
113+
Terminal::AndB(stack.pop().unwrap(), stack.pop().unwrap())
114+
}
115+
Terminal::AndOr(..) => Terminal::AndOr(
116+
stack.pop().unwrap(),
117+
stack.pop().unwrap(),
118+
stack.pop().unwrap(),
120119
),
120+
Terminal::OrB(..) => Terminal::OrB(stack.pop().unwrap(), stack.pop().unwrap()),
121+
Terminal::OrD(..) => Terminal::OrD(stack.pop().unwrap(), stack.pop().unwrap()),
122+
Terminal::OrC(..) => Terminal::OrC(stack.pop().unwrap(), stack.pop().unwrap()),
123+
Terminal::OrI(..) => Terminal::OrI(stack.pop().unwrap(), stack.pop().unwrap()),
124+
Terminal::Thresh(ref thresh) => {
125+
Terminal::Thresh(thresh.map_ref(|_| stack.pop().unwrap()))
126+
}
121127
Terminal::Multi(ref thresh) => Terminal::Multi(thresh.clone()),
122128
Terminal::MultiA(ref thresh) => Terminal::MultiA(thresh.clone()),
123129
};
@@ -130,6 +136,7 @@ mod private {
130136
}));
131137
}
132138

139+
assert_eq!(stack.len(), 1);
133140
Arc::try_unwrap(stack.pop().unwrap()).unwrap()
134141
}
135142
}
@@ -536,9 +543,7 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
536543
T: Translator<Pk, Q, FuncError>,
537544
{
538545
let mut translated = vec![];
539-
for data in Arc::new(self.clone()).post_order_iter() {
540-
let child_n = |n| Arc::clone(&translated[data.child_indices[n]]);
541-
546+
for data in self.rtl_post_order_iter() {
542547
let new_term = match data.node.node {
543548
Terminal::PkK(ref p) => Terminal::PkK(t.pk(p)?),
544549
Terminal::PkH(ref p) => Terminal::PkH(t.pk(p)?),
@@ -551,23 +556,39 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
551556
Terminal::Hash160(ref x) => Terminal::Hash160(t.hash160(x)?),
552557
Terminal::True => Terminal::True,
553558
Terminal::False => Terminal::False,
554-
Terminal::Alt(..) => Terminal::Alt(child_n(0)),
555-
Terminal::Swap(..) => Terminal::Swap(child_n(0)),
556-
Terminal::Check(..) => Terminal::Check(child_n(0)),
557-
Terminal::DupIf(..) => Terminal::DupIf(child_n(0)),
558-
Terminal::Verify(..) => Terminal::Verify(child_n(0)),
559-
Terminal::NonZero(..) => Terminal::NonZero(child_n(0)),
560-
Terminal::ZeroNotEqual(..) => Terminal::ZeroNotEqual(child_n(0)),
561-
Terminal::AndV(..) => Terminal::AndV(child_n(0), child_n(1)),
562-
Terminal::AndB(..) => Terminal::AndB(child_n(0), child_n(1)),
563-
Terminal::AndOr(..) => Terminal::AndOr(child_n(0), child_n(1), child_n(2)),
564-
Terminal::OrB(..) => Terminal::OrB(child_n(0), child_n(1)),
565-
Terminal::OrD(..) => Terminal::OrD(child_n(0), child_n(1)),
566-
Terminal::OrC(..) => Terminal::OrC(child_n(0), child_n(1)),
567-
Terminal::OrI(..) => Terminal::OrI(child_n(0), child_n(1)),
568-
Terminal::Thresh(ref thresh) => Terminal::Thresh(
569-
thresh.map_from_post_order_iter(&data.child_indices, &translated),
559+
Terminal::Alt(..) => Terminal::Alt(translated.pop().unwrap()),
560+
Terminal::Swap(..) => Terminal::Swap(translated.pop().unwrap()),
561+
Terminal::Check(..) => Terminal::Check(translated.pop().unwrap()),
562+
Terminal::DupIf(..) => Terminal::DupIf(translated.pop().unwrap()),
563+
Terminal::Verify(..) => Terminal::Verify(translated.pop().unwrap()),
564+
Terminal::NonZero(..) => Terminal::NonZero(translated.pop().unwrap()),
565+
Terminal::ZeroNotEqual(..) => Terminal::ZeroNotEqual(translated.pop().unwrap()),
566+
Terminal::AndV(..) => {
567+
Terminal::AndV(translated.pop().unwrap(), translated.pop().unwrap())
568+
}
569+
Terminal::AndB(..) => {
570+
Terminal::AndB(translated.pop().unwrap(), translated.pop().unwrap())
571+
}
572+
Terminal::AndOr(..) => Terminal::AndOr(
573+
translated.pop().unwrap(),
574+
translated.pop().unwrap(),
575+
translated.pop().unwrap(),
570576
),
577+
Terminal::OrB(..) => {
578+
Terminal::OrB(translated.pop().unwrap(), translated.pop().unwrap())
579+
}
580+
Terminal::OrD(..) => {
581+
Terminal::OrD(translated.pop().unwrap(), translated.pop().unwrap())
582+
}
583+
Terminal::OrC(..) => {
584+
Terminal::OrC(translated.pop().unwrap(), translated.pop().unwrap())
585+
}
586+
Terminal::OrI(..) => {
587+
Terminal::OrI(translated.pop().unwrap(), translated.pop().unwrap())
588+
}
589+
Terminal::Thresh(ref thresh) => {
590+
Terminal::Thresh(thresh.map_ref(|_| translated.pop().unwrap()))
591+
}
571592
Terminal::Multi(ref thresh) => Terminal::Multi(thresh.translate_ref(|k| t.pk(k))?),
572593
Terminal::MultiA(ref thresh) => {
573594
Terminal::MultiA(thresh.translate_ref(|k| t.pk(k))?)
@@ -582,22 +603,58 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
582603

583604
/// Substitutes raw public keys hashes with the public keys as provided by map.
584605
pub fn substitute_raw_pkh(&self, pk_map: &BTreeMap<hash160::Hash, Pk>) -> Miniscript<Pk, Ctx> {
585-
let mut translated = vec![];
586-
for data in Arc::new(self.clone()).post_order_iter() {
587-
let new_term = if let Terminal::RawPkH(ref p) = data.node.node {
588-
match pk_map.get(p) {
589-
Some(pk) => Terminal::PkH(pk.clone()),
590-
None => Terminal::RawPkH(*p),
606+
let mut stack = vec![];
607+
for item in self.rtl_post_order_iter() {
608+
let new_term = match item.node.node {
609+
Terminal::PkK(ref p) => Terminal::PkK(p.clone()),
610+
Terminal::PkH(ref p) => Terminal::PkH(p.clone()),
611+
// This algorithm is identical to Clone::clone except for this line.
612+
Terminal::RawPkH(ref hash) => match pk_map.get(hash) {
613+
Some(p) => Terminal::PkH(p.clone()),
614+
None => Terminal::RawPkH(*hash),
615+
},
616+
Terminal::After(ref n) => Terminal::After(*n),
617+
Terminal::Older(ref n) => Terminal::Older(*n),
618+
Terminal::Sha256(ref x) => Terminal::Sha256(x.clone()),
619+
Terminal::Hash256(ref x) => Terminal::Hash256(x.clone()),
620+
Terminal::Ripemd160(ref x) => Terminal::Ripemd160(x.clone()),
621+
Terminal::Hash160(ref x) => Terminal::Hash160(x.clone()),
622+
Terminal::True => Terminal::True,
623+
Terminal::False => Terminal::False,
624+
Terminal::Alt(..) => Terminal::Alt(stack.pop().unwrap()),
625+
Terminal::Swap(..) => Terminal::Swap(stack.pop().unwrap()),
626+
Terminal::Check(..) => Terminal::Check(stack.pop().unwrap()),
627+
Terminal::DupIf(..) => Terminal::DupIf(stack.pop().unwrap()),
628+
Terminal::Verify(..) => Terminal::Verify(stack.pop().unwrap()),
629+
Terminal::NonZero(..) => Terminal::NonZero(stack.pop().unwrap()),
630+
Terminal::ZeroNotEqual(..) => Terminal::ZeroNotEqual(stack.pop().unwrap()),
631+
Terminal::AndV(..) => Terminal::AndV(stack.pop().unwrap(), stack.pop().unwrap()),
632+
Terminal::AndB(..) => Terminal::AndB(stack.pop().unwrap(), stack.pop().unwrap()),
633+
Terminal::AndOr(..) => Terminal::AndOr(
634+
stack.pop().unwrap(),
635+
stack.pop().unwrap(),
636+
stack.pop().unwrap(),
637+
),
638+
Terminal::OrB(..) => Terminal::OrB(stack.pop().unwrap(), stack.pop().unwrap()),
639+
Terminal::OrD(..) => Terminal::OrD(stack.pop().unwrap(), stack.pop().unwrap()),
640+
Terminal::OrC(..) => Terminal::OrC(stack.pop().unwrap(), stack.pop().unwrap()),
641+
Terminal::OrI(..) => Terminal::OrI(stack.pop().unwrap(), stack.pop().unwrap()),
642+
Terminal::Thresh(ref thresh) => {
643+
Terminal::Thresh(thresh.map_ref(|_| stack.pop().unwrap()))
591644
}
592-
} else {
593-
data.node.node.clone()
645+
Terminal::Multi(ref thresh) => Terminal::Multi(thresh.clone()),
646+
Terminal::MultiA(ref thresh) => Terminal::MultiA(thresh.clone()),
594647
};
595648

596-
let new_ms = Miniscript::from_ast(new_term).expect("typeck");
597-
translated.push(Arc::new(new_ms));
649+
stack.push(Arc::new(Miniscript::from_components_unchecked(
650+
new_term,
651+
item.node.ty,
652+
item.node.ext,
653+
)));
598654
}
599655

600-
Arc::try_unwrap(translated.pop().unwrap()).unwrap()
656+
assert_eq!(stack.len(), 1);
657+
Arc::try_unwrap(stack.pop().unwrap()).unwrap()
601658
}
602659
}
603660

@@ -822,6 +879,7 @@ mod tests {
822879
}
823880
let roundtrip = Miniscript::from_str(&display).expect("parse string serialization");
824881
assert_eq!(roundtrip, script);
882+
assert_eq!(roundtrip.clone(), script);
825883
}
826884

827885
fn string_display_debug_test<Ctx: ScriptContext>(

0 commit comments

Comments
 (0)