twiggy_ir/
ir.rs

1//! The `twiggy` code size profiler.
2
3#![deny(missing_docs)]
4#![deny(missing_debug_implementations)]
5
6mod graph_impl;
7
8use frozen::Frozen;
9use std::cmp;
10use std::collections::btree_map;
11use std::collections::{BTreeMap, BTreeSet};
12use std::ops;
13use std::slice;
14use std::u32;
15
16/// Build up a a set of `Items`.
17#[derive(Debug)]
18pub struct ItemsBuilder {
19    size: u32,
20    size_added: u32,
21    parsed: BTreeSet<Id>,
22    items: BTreeMap<Id, Item>,
23    edges: BTreeMap<Id, BTreeSet<Id>>,
24    roots: BTreeSet<Id>,
25
26    // Maps the offset some data begins at to its IR item's identifier, and the
27    // byte length of the data.
28    data: BTreeMap<u32, (Id, u32)>,
29}
30
31impl ItemsBuilder {
32    /// Construct a new builder, with the given size.
33    pub fn new(size: u32) -> ItemsBuilder {
34        ItemsBuilder {
35            size,
36            size_added: 0,
37            parsed: Default::default(),
38            items: Default::default(),
39            edges: Default::default(),
40            roots: Default::default(),
41            data: Default::default(),
42        }
43    }
44
45    /// Add the given item to to the graph and return the `Id` that it was
46    /// assigned.
47    pub fn add_item(&mut self, item: Item) -> Id {
48        let id = item.id;
49        self.size_added += item.size;
50        self.items.insert(id, item);
51
52        let old_value = self.parsed.insert(id);
53        assert!(
54            old_value,
55            "should not parse the same key into multiple items"
56        );
57
58        id
59    }
60
61    /// Add the given item to the graph as a root and return the `Id` that it
62    /// was assigned.
63    pub fn add_root(&mut self, item: Item) -> Id {
64        let id = self.add_item(item);
65        self.roots.insert(id);
66        id
67    }
68
69    /// Add an edge between the given keys that have already been parsed into
70    /// items.
71    pub fn add_edge(&mut self, from: Id, to: Id) {
72        debug_assert!(self.items.contains_key(&from), "`from` is not known");
73        debug_assert!(self.items.contains_key(&to), "`to` is not known");
74
75        self.edges
76            .entry(from)
77            .or_insert_with(BTreeSet::new)
78            .insert(to);
79    }
80
81    /// Add a range of static data and the `Id` that defines it.
82    pub fn link_data(&mut self, offset: i64, len: usize, id: Id) {
83        if offset >= 0 && offset <= i64::from(u32::MAX) && offset as usize + len < u32::MAX as usize
84        {
85            self.data.insert(offset as u32, (id, len as u32));
86        }
87    }
88
89    /// Locate the data section defining memory at the given offset.
90    pub fn get_data(&self, offset: u32) -> Option<Id> {
91        self.data
92            .range(offset..)
93            .next()
94            .and_then(
95                |(start, &(id, len))| {
96                    if offset < start + len {
97                        Some(id)
98                    } else {
99                        None
100                    }
101                },
102            )
103    }
104
105    /// Return the size of all added items so far
106    pub fn size_added(&self) -> u32 {
107        self.size_added
108    }
109
110    /// Finish building the IR graph and return the resulting `Items`.
111    pub fn finish(mut self) -> Items {
112        let meta_root_id = Id::root();
113        let meta_root = Item::new(meta_root_id, "<meta root>", 0, Misc::new());
114        self.items.insert(meta_root_id, meta_root);
115        self.edges.insert(meta_root_id, self.roots.clone());
116
117        Items {
118            size: self.size,
119            dominator_tree: None,
120            retained_sizes: None,
121            predecessors: None,
122            immediate_dominators: None,
123            items: Frozen::freeze(self.items),
124            edges: Frozen::freeze(
125                self.edges
126                    .into_iter()
127                    .map(|(from, tos)| (from, tos.into_iter().collect::<Vec<_>>()))
128                    .collect(),
129            ),
130            roots: Frozen::freeze(self.roots),
131            meta_root: meta_root_id,
132        }
133    }
134}
135
136/// The architecture- and target-independent internal representation of
137/// functions, sections, etc in a file that is being size profiled.
138///
139/// Constructed with `ItemsBuilder`.
140#[derive(Debug)]
141pub struct Items {
142    size: u32,
143    dominator_tree: Option<BTreeMap<Id, Vec<Id>>>,
144    immediate_dominators: Option<BTreeMap<Id, Id>>,
145    retained_sizes: Option<BTreeMap<Id, u32>>,
146    predecessors: Option<BTreeMap<Id, Vec<Id>>>,
147    items: Frozen<BTreeMap<Id, Item>>,
148    edges: Frozen<BTreeMap<Id, Vec<Id>>>,
149    roots: Frozen<BTreeSet<Id>>,
150    meta_root: Id,
151}
152
153impl ops::Index<Id> for Items {
154    type Output = Item;
155
156    fn index(&self, id: Id) -> &Item {
157        &self.items[&id]
158    }
159}
160
161impl Items {
162    /// Iterate over all of the IR items.
163    pub fn iter(&self) -> Iter {
164        Iter {
165            inner: self.items.iter(),
166        }
167    }
168
169    /// Iterate over an item's neighbors.
170    pub fn neighbors(&self, id: Id) -> Neighbors {
171        Neighbors {
172            inner: self
173                .edges
174                .get(&id)
175                .map_or_else(|| [].iter(), |edges| edges.iter()),
176        }
177    }
178
179    /// Iterate over an item's predecessors.
180    pub fn predecessors(&self, id: Id) -> Predecessors {
181        Predecessors {
182            inner: self
183                .predecessors
184                .as_ref()
185                .expect("To access predecessors, must have already called compute_predecessors")
186                .get(&id)
187                .map_or_else(|| [].iter(), |edges| edges.iter()),
188        }
189    }
190
191    /// The size of the total binary, containing all items.
192    pub fn size(&self) -> u32 {
193        self.size
194    }
195
196    /// Get the id of the "meta root" which is a single root item with edges to
197    /// all of the real roots.
198    pub fn meta_root(&self) -> Id {
199        self.meta_root
200    }
201
202    /// Force computation of predecessors.
203    pub fn compute_predecessors(&mut self) {
204        if self.predecessors.is_some() {
205            return;
206        }
207
208        let mut predecessors = BTreeMap::new();
209
210        for (from, tos) in self.edges.iter() {
211            for to in tos {
212                predecessors
213                    .entry(*to)
214                    .or_insert_with(BTreeSet::new)
215                    .insert(*from);
216            }
217        }
218
219        self.predecessors = Some(
220            predecessors
221                .into_iter()
222                .map(|(k, v)| (k, v.into_iter().collect()))
223                .collect(),
224        );
225    }
226
227    /// Compute dominators for each item.
228    pub fn compute_dominators(&mut self) {
229        if self.immediate_dominators.is_some() {
230            return;
231        }
232
233        let mut immediate_dominators = BTreeMap::new();
234        let dominators = petgraph::algo::dominators::simple_fast(&*self, self.meta_root);
235
236        for item in self.iter() {
237            if let Some(idom) = dominators.immediate_dominator(item.id()) {
238                immediate_dominators.insert(item.id(), idom);
239            }
240        }
241
242        self.immediate_dominators = Some(immediate_dominators);
243    }
244
245    /// Get a refercence to immediate dominators
246    pub fn immediate_dominators(&self) -> &BTreeMap<Id, Id> {
247        self.immediate_dominators
248            .as_ref()
249            .expect("must call compute_immediate_dominators before calling immediate_dominators")
250    }
251
252    /// Force computation of the dominator tree.
253    pub fn compute_dominator_tree(&mut self) {
254        if self.dominator_tree.is_some() {
255            return;
256        }
257
258        let mut dominator_tree = BTreeMap::new();
259        let dominators = petgraph::algo::dominators::simple_fast(&*self, self.meta_root);
260        for item in self.iter() {
261            if let Some(idom) = dominators.immediate_dominator(item.id()) {
262                dominator_tree
263                    .entry(idom)
264                    .or_insert_with(BTreeSet::new)
265                    .insert(item.id());
266            }
267        }
268
269        self.dominator_tree = Some(
270            dominator_tree
271                .into_iter()
272                .map(|(k, v)| (k, v.into_iter().collect()))
273                .collect(),
274        );
275    }
276
277    /// Get a reference to the dominator tree.
278    ///
279    /// Must have already called `compute_dominator_tree`.
280    pub fn dominator_tree(&self) -> &BTreeMap<Id, Vec<Id>> {
281        self.dominator_tree
282            .as_ref()
283            .expect("must call compute_dominator_tree before calling dominator_tree")
284    }
285
286    /// Force computation of the retained sizes of each IR item.
287    pub fn compute_retained_sizes(&mut self) {
288        if self.retained_sizes.is_some() {
289            return;
290        }
291        self.compute_dominator_tree();
292
293        fn recursive_retained_size(
294            retained_sizes: &mut BTreeMap<Id, u32>,
295            items: &Items,
296            item: &Item,
297            dominator_tree: &BTreeMap<Id, Vec<Id>>,
298        ) -> u32 {
299            // Although the dominator tree cannot have cycles, because we
300            // compute retained sizes in item iteration order, rather than from
301            // the bottom of the dominator tree up, it is possible we have
302            // already computed the retained sizes for subtrees.
303            if let Some(rsize) = retained_sizes.get(&item.id()) {
304                return *rsize;
305            }
306
307            let mut rsize = item.size();
308            if let Some(children) = dominator_tree.get(&item.id()) {
309                for child in children {
310                    rsize += recursive_retained_size(
311                        retained_sizes,
312                        items,
313                        &items[*child],
314                        dominator_tree,
315                    );
316                }
317            }
318
319            let old_value = retained_sizes.insert(item.id(), rsize);
320            // The dominator tree is a proper tree, so there shouldn't be
321            // any cycles.
322            assert!(old_value.is_none());
323            rsize
324        }
325
326        let mut retained_sizes = BTreeMap::new();
327        {
328            let dominator_tree = self.dominator_tree.as_ref().unwrap();
329            for item in self.iter() {
330                recursive_retained_size(&mut retained_sizes, self, item, dominator_tree);
331            }
332        }
333        self.retained_sizes = Some(retained_sizes);
334    }
335
336    /// Get the given item's retained size.
337    pub fn retained_size(&self, id: Id) -> u32 {
338        self.retained_sizes
339            .as_ref()
340            .expect(
341                "Cannot call retained_sizes unless compute_retained_sizes \
342                 has already been called",
343            )
344            .get(&id)
345            .cloned()
346            .unwrap()
347    }
348
349    /// Get an item with the given name.
350    pub fn get_item_by_name(&self, name: &str) -> Option<&Item> {
351        for item in self.iter() {
352            if item.name() == name {
353                return Some(item);
354            }
355        }
356
357        None // Return `None` if `name` did not match any items.
358    }
359}
360
361/// An iterator over an item's neighbors.
362#[derive(Debug)]
363pub struct Neighbors<'a> {
364    inner: slice::Iter<'a, Id>,
365}
366
367impl<'a> Iterator for Neighbors<'a> {
368    type Item = Id;
369
370    #[inline]
371    fn next(&mut self) -> Option<Id> {
372        self.inner.next().cloned()
373    }
374}
375
376/// An iterator over an item's predecessors.
377#[derive(Debug)]
378pub struct Predecessors<'a> {
379    inner: slice::Iter<'a, Id>,
380}
381
382impl<'a> Iterator for Predecessors<'a> {
383    type Item = Id;
384
385    #[inline]
386    fn next(&mut self) -> Option<Id> {
387        self.inner.next().cloned()
388    }
389}
390
391/// An iterator over IR items. Created by `Items::iter`.
392#[derive(Clone, Debug)]
393pub struct Iter<'a> {
394    inner: btree_map::Iter<'a, Id, Item>,
395}
396
397impl<'a> Iterator for Iter<'a> {
398    type Item = &'a Item;
399
400    fn next(&mut self) -> Option<Self::Item> {
401        self.inner.next().map(|(_, item)| item)
402    }
403}
404
405/// An item's unique identifier.
406/// (section index, item within that section index)
407#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
408pub struct Id(u32, u32);
409
410impl Id {
411    /// Create an `Id` for a the given section.
412    pub fn section(section: usize) -> Id {
413        assert!(section < u32::MAX as usize);
414        Id(section as u32, u32::MAX)
415    }
416
417    /// Create an `Id` for a given entry in a given section.
418    pub fn entry(section: usize, index: usize) -> Id {
419        assert!(section < u32::MAX as usize);
420        assert!(index < u32::MAX as usize);
421        Id(section as u32, index as u32)
422    }
423
424    /// Create the `Id` for the "meta root".
425    pub fn root() -> Id {
426        Id(u32::MAX, u32::MAX)
427    }
428
429    /// Get the real id of a item.
430    pub fn serializable(self) -> u64 {
431        let top = (u64::from(self.0)) << 32;
432        top | u64::from(self.1)
433    }
434}
435
436/// An item in the binary.
437#[derive(Clone, Debug, PartialEq, Eq)]
438pub struct Item {
439    id: Id,
440    name: String,
441    size: u32,
442    kind: ItemKind,
443}
444
445impl Item {
446    /// Construct a new `Item` of the given kind.
447    pub fn new<S, K>(id: Id, name: S, size: u32, kind: K) -> Item
448    where
449        S: Into<String>,
450        K: Into<ItemKind>,
451    {
452        let name = name.into();
453        Item {
454            id,
455            name,
456            size,
457            kind: kind.into(),
458        }
459    }
460
461    /// Get this item's identifier.
462    #[inline]
463    pub fn id(&self) -> Id {
464        self.id
465    }
466
467    /// Get this item's size.
468    #[inline]
469    pub fn size(&self) -> u32 {
470        self.size
471    }
472
473    /// Get this item's name.
474    #[inline]
475    pub fn name(&self) -> &str {
476        if let ItemKind::Code(ref code) = self.kind {
477            code.demangled().unwrap_or(&self.name)
478        } else {
479            &self.name
480        }
481    }
482
483    /// Get this item's kind.
484    #[inline]
485    pub fn kind(&self) -> &ItemKind {
486        &self.kind
487    }
488
489    /// The the name of the generic function that this is a monomorphization of
490    /// (if any).
491    #[inline]
492    pub fn monomorphization_of(&self) -> Option<&str> {
493        if let ItemKind::Code(ref code) = self.kind {
494            code.monomorphization_of()
495        } else {
496            None
497        }
498    }
499}
500
501impl PartialOrd for Item {
502    fn partial_cmp(&self, rhs: &Item) -> Option<cmp::Ordering> {
503        self.id.partial_cmp(&rhs.id)
504    }
505}
506
507impl Ord for Item {
508    fn cmp(&self, rhs: &Item) -> cmp::Ordering {
509        self.id.cmp(&rhs.id)
510    }
511}
512
513/// The kind of item in the binary.
514#[derive(Clone, Debug, PartialEq, Eq)]
515pub enum ItemKind {
516    /// Executable code. Function bodies.
517    Code(Code),
518
519    /// Data inside the binary that may or may not end up loaded into memory
520    /// with the executable code.
521    Data(Data),
522
523    /// Debugging symbols and information, such as a DWARF section.
524    Debug(DebugInfo),
525
526    /// Miscellaneous item. Perhaps metadata. Perhaps something else.
527    Misc(Misc),
528}
529
530impl ItemKind {
531    /// Returns true if `self` is the `Data` variant
532    pub fn is_data(&self) -> bool {
533        match self {
534            ItemKind::Data(_) => true,
535            _ => false,
536        }
537    }
538}
539
540impl From<Code> for ItemKind {
541    fn from(c: Code) -> ItemKind {
542        ItemKind::Code(c)
543    }
544}
545
546impl From<Data> for ItemKind {
547    fn from(d: Data) -> ItemKind {
548        ItemKind::Data(d)
549    }
550}
551
552impl From<DebugInfo> for ItemKind {
553    fn from(d: DebugInfo) -> ItemKind {
554        ItemKind::Debug(d)
555    }
556}
557
558impl From<Misc> for ItemKind {
559    fn from(m: Misc) -> ItemKind {
560        ItemKind::Misc(m)
561    }
562}
563
564/// Executable code. Function bodies.
565#[derive(Clone, Debug, PartialEq, Eq)]
566pub struct Code {
567    demangled: Option<String>,
568    monomorphization_of: Option<String>,
569}
570
571impl Code {
572    /// Construct a new IR item for executable code.
573    pub fn new(name: &str) -> Code {
574        let demangled = Self::demangle(&name);
575        let monomorphization_of =
576            Self::extract_generic_function(demangled.as_ref().map(|s| s.as_str()).unwrap_or(name));
577        Code {
578            demangled,
579            monomorphization_of,
580        }
581    }
582
583    /// Get the demangled name of this function, if any.
584    pub fn demangled(&self) -> Option<&str> {
585        self.demangled.as_ref().map(|s| s.as_str())
586    }
587
588    /// Get the name of the generic function that this is a monomorphization of,
589    /// if any.
590    pub fn monomorphization_of(&self) -> Option<&str> {
591        self.monomorphization_of.as_ref().map(|s| s.as_str())
592    }
593
594    fn demangle(s: &str) -> Option<String> {
595        if let Ok(sym) = rustc_demangle::try_demangle(s) {
596            return Some(sym.to_string());
597        }
598
599        // If the Rust demangle failed, we'll try C or C++.  C++
600        // symbols almost all start with the prefixes "_Z", "__Z", and
601        // ""_GLOBAL_", except for a special case.
602        //
603        // Per cpp_mangle::ast::MangledName::parse:
604        //
605        // > The libiberty tests also specify that a type can be top level,
606        // > and they are not prefixed with "_Z".
607        //
608        // Therefore cpp_demangle will parse unmangled symbols, at
609        // least sometimes incorrectly (e.g. with OpenSSL's RC4
610        // function, which is incorrectly parsed as a type ctor/dtor),
611        // which confuses a subsequent `demangle` function, resulting
612        // in panic.
613        //
614        // To avoid that, only pass C++-mangled symbols to the C++
615        // demangler
616        if !s.starts_with("_Z") && !s.starts_with("__Z") && !s.starts_with("_GLOBAL_") {
617            return Some(s.to_string());
618        }
619
620        if let Ok(sym) = cpp_demangle::Symbol::new(s) {
621            return Some(sym.to_string());
622        }
623
624        None
625    }
626
627    fn extract_generic_function(demangled: &str) -> Option<String> {
628        // XXX: This is some hacky, ad-hoc parsing shit! This should
629        // approximately work for Rust and C++ symbols, but who knows for other
630        // languages. Also, it almost definitely has bugs!
631
632        // First, check for Rust-style symbols by looking for Rust's
633        // "::h1234567890" hash from the end of the symbol. If it's there, the
634        // generic function is just the symbol without that hash, so remove it.
635        //
636        // I know what you're thinking, and it's true: mangled (and therefore
637        // also demangled) Rust symbols don't include the concrete type(s) used
638        // to instantiate the generic function, which gives us much less to work
639        // with than we have with C++ demangled symbols. It would sure be nice
640        // if we could tell the user more about the monomorphization, but
641        // alas... :(
642        if let Some(idx) = demangled.rfind("::h") {
643            let idx2 = demangled.rfind("::").unwrap();
644            assert!(idx2 >= idx);
645            if idx2 == idx {
646                let generic = demangled[..idx].to_string();
647                return Some(generic);
648            }
649        }
650
651        // From here on out, we assume we are dealing with C++ symbols.
652        //
653        // Find the '<' and '>' that hug the generic type(s).
654        let open_bracket = match demangled.char_indices().find(|&(_, ch)| ch == '<') {
655            None => return None,
656            Some((idx, _)) => idx,
657        };
658        let close_bracket = match demangled.char_indices().rev().find(|&(_, ch)| ch == '>') {
659            None => return None,
660            Some((idx, _)) => idx,
661        };
662
663        // If the '<' doesn't come before the '>', then we aren't looking at a
664        // generic function instantiation. If there isn't anything proceeding
665        // the '<', then we aren't looking at a generic function instantiation
666        // (most likely looking at a Rust trait method's implementation, like
667        // `<MyType as SomeTrait>::trait_method()`).
668        if close_bracket < open_bracket || open_bracket == 0 {
669            return None;
670        }
671
672        // And now we say that the generic function is the thing proceeding the
673        // '<'. Good enough!
674        Some(demangled[..open_bracket].to_string())
675    }
676}
677
678/// Data inside the binary that may or may not end up loaded into memory
679/// with the executable code.
680#[derive(Clone, Debug, PartialEq, Eq)]
681pub struct Data {
682    ty: Option<String>,
683}
684
685impl Data {
686    /// Construct a new `Data` that has a type of the given type name, if known.
687    pub fn new(ty: Option<String>) -> Data {
688        Data { ty }
689    }
690}
691
692/// Debugging symbols and information, such as DWARF sections.
693#[derive(Clone, Debug, PartialEq, Eq, Default)]
694pub struct DebugInfo;
695
696impl DebugInfo {
697    /// Construct a new IR item for debug information and symbols.
698    pub fn new() -> DebugInfo {
699        DebugInfo
700    }
701}
702
703/// Miscellaneous item. Perhaps metadata. Perhaps something else.
704#[derive(Clone, Debug, PartialEq, Eq, Default)]
705pub struct Misc;
706
707impl Misc {
708    /// Construct a new miscellaneous IR item.
709    pub fn new() -> Misc {
710        Misc
711    }
712}