yz_basic_block/arena/
optimize.rs

1use super::Arena;
2use crate::bb::BasicBlockInner;
3use crate::jump::{self, ForeachTarget};
4use crate::BbId;
5use alloc::collections::{BTreeMap as Map, BTreeSet};
6use alloc::vec::Vec;
7use core::mem::{drop, take};
8
9/// Temporary information describing the modifications to be done
10/// and cached data, per BbId.
11///
12/// This structure is at least implicitly wrapped in an `Option` (key exists?),
13/// if Some, then TR $key->$target,
14/// otherwise, remove every occurence of $key if possible,
15/// panic otherwise (especially if otherwise invariants would be violated)
16///
17/// To avoid reallocations and multiple intermediate data types,
18/// this structure goes through multiple stages, which contain
19/// similiar, but not equivalent sets of data.
20#[derive(Clone)]
21struct TransInfo {
22    /// for $key->$target search-and-replace; default = $key
23    target: usize,
24
25    /// references from $i pointing to $key,
26    /// useful for optimizations which rely on the fact that only
27    /// one other BB depends on a BB, then they're mergable
28    /// default = 1
29    refs: BTreeSet<usize>,
30}
31
32impl TransInfo {
33    #[inline]
34    fn new(id: usize) -> Self {
35        Self {
36            target: id,
37            refs: BTreeSet::new(),
38        }
39    }
40}
41
42/// check function which makes sure that no reference to $exclude exists
43/// inside of $container.
44fn fetchk<C>(is_mergable: &mut bool, container: &C, exclude: <C as ForeachTarget>::JumpTarget)
45where
46    C: ForeachTarget,
47    C::JumpTarget: Copy + PartialEq,
48{
49    container.foreach_target(move |&t| {
50        if exclude == t {
51            *is_mergable = false;
52        }
53    });
54}
55
56impl<S, C> Arena<S, C>
57where
58    S: ForeachTarget<JumpTarget = BbId>,
59    C: ForeachTarget<JumpTarget = BbId>,
60{
61    pub fn optimize(&mut self) -> bool {
62        let mut new_in_use = Vec::with_capacity(self.bbs.len());
63        let mut trm: Map<BbId, Option<TransInfo>> = Map::new();
64
65        for (&from, i) in self.bbs.iter() {
66            if i.is_public {
67                new_in_use.push(from);
68            } else if let BasicBlockInner::Concrete {
69                statements,
70                condjmp,
71                next,
72            } = &i.inner
73            {
74                if statements.is_empty() && condjmp.is_none() {
75                    if let jump::Unconditional::Jump(trg) = *next {
76                        if from != trg {
77                            trm.entry(from)
78                                .or_insert_with(|| Some(TransInfo::new(from)))
79                                .as_mut()
80                                .unwrap()
81                                .target = trg;
82                        }
83                    }
84                }
85            }
86        }
87
88        // recursively mark anything as in-use only if reachable from in-use or pub
89        let mut in_use = BTreeSet::new();
90        while !new_in_use.is_empty() {
91            for i in take(&mut new_in_use) {
92                if let Some(ent) = self.bbs.get(&i) {
93                    if in_use.insert(i) {
94                        // really new entry
95                        ent.foreach_target(|&trg| {
96                            trm.entry(trg)
97                                .or_insert_with(|| Some(TransInfo::new(trg)))
98                                .as_mut()
99                                .unwrap()
100                                .refs
101                                .insert(i);
102                            new_in_use.push(trg);
103                        });
104                    }
105                }
106            }
107        }
108        drop(new_in_use);
109
110        // check all references for one-refs (which may be mergable)
111        for (n, ti) in trm
112            .iter()
113            .filter_map(|(&n, ti)| ti.as_ref().map(|ti2| (n, ti2)))
114        {
115            if ti.refs.len() != 1 {
116                continue;
117            }
118            let bbheadref = *ti.refs.iter().next().unwrap();
119            if bbheadref == n {
120                continue;
121            }
122            if trm
123                .get(&bbheadref)
124                .and_then(Option::as_ref)
125                .map(|hti| hti.target != bbheadref)
126                == Some(true)
127            {
128                // head is already redirected, skip
129                continue;
130            }
131            let mut is_mergable = false;
132            if let Some(bbhead) = self.bbs.get_mut(&bbheadref) {
133                if let BasicBlockInner::Concrete {
134                    statements,
135                    condjmp,
136                    next,
137                } = &mut bbhead.inner
138                {
139                    is_mergable = condjmp.is_none() && *next == jump::Unconditional::Jump(n);
140
141                    // make sure that we don't have any additional references to bbtail
142                    fetchk(&mut is_mergable, statements, n);
143                }
144            }
145            if !is_mergable {
146                continue;
147            }
148            let bbtail = if let Some(bbtail) = self.bbs.get_mut(&n) {
149                if bbtail.is_public || !bbtail.inner.is_concrete() {
150                    continue;
151                }
152                fetchk(&mut is_mergable, bbtail, n);
153                if !is_mergable {
154                    continue;
155                }
156                take(&mut bbtail.inner)
157            } else {
158                continue;
159            };
160
161            // mergable
162            if let BasicBlockInner::Concrete {
163                mut statements,
164                condjmp,
165                next,
166            } = bbtail
167            {
168                if let BasicBlockInner::Concrete {
169                    statements: ref mut h_statements,
170                    condjmp: ref mut h_condjmp,
171                    next: ref mut h_next,
172                } = &mut self.bbs.get_mut(&bbheadref).unwrap().inner
173                {
174                    in_use.remove(&n);
175                    if h_statements.is_empty() {
176                        // this normally only happens if $head.is_public
177                        // merge labels manually
178                        for ltrg in self.labels.values_mut() {
179                            if *ltrg == n {
180                                *ltrg = bbheadref;
181                            }
182                        }
183                    }
184                    h_statements.append(&mut statements);
185                    *h_condjmp = condjmp;
186                    *h_next = next;
187                } else {
188                    unreachable!();
189                }
190            } else {
191                unreachable!();
192            }
193        }
194
195        // apply in_use
196        let mut modified = false;
197        for i in self.bbs.keys() {
198            let e = trm.entry(*i);
199            if in_use.contains(i) {
200                if let alloc::collections::btree_map::Entry::Occupied(mut e) = e {
201                    if let Some(ti) = e.get_mut() {
202                        if *i != ti.target {
203                            modified = true;
204                        } else {
205                            e.remove_entry();
206                        }
207                    }
208                }
209            } else {
210                *e.or_default() = None;
211                modified = true;
212            }
213        }
214        if !modified {
215            // nothing left to do, we are done
216            return false;
217        }
218
219        // remove to-be-removed BBs
220        {
221            let mut it = trm.iter().filter(|x| x.1.is_none()).map(|x| x.0);
222            if let Some(&nfi) = it.next() {
223                if nfi < self.cache_ins_start {
224                    self.cache_ins_start = nfi;
225                }
226                self.bbs.remove(&nfi);
227                for n in it {
228                    self.bbs.remove(&n);
229                }
230            }
231        }
232
233        // replace jump targets
234        for i in self.bbs.values_mut() {
235            i.foreach_target_mut(|target| {
236                if let Some(Some(ti)) = trm.get(target) {
237                    *target = ti.target;
238                }
239            });
240        }
241
242        self.labels = take(&mut self.labels)
243            .into_iter()
244            .filter_map(|(label, bbid)| {
245                // remove all labels which point to a BBID which should be deleted,
246                // either because it is marked in trm -> None, or the BBID is invalid.
247                // update all remaining labels
248                match trm.get(&bbid) {
249                    Some(None) => None,
250                    Some(Some(ti)) => Some(ti.target),
251                    None => Some(bbid),
252                }
253                .map(|nn| (label, nn))
254            })
255            .collect();
256
257        true
258    }
259}