bend/fun/transform/
definition_pruning.rs

1use crate::{
2  diagnostics::WarningType,
3  fun::{Book, Ctx, Name, SourceKind, Term},
4  maybe_grow,
5};
6use hvm::ast::{Net, Tree};
7use std::collections::{hash_map::Entry, HashMap};
8
9#[derive(Clone, Copy, Debug, PartialEq)]
10enum Used {
11  /// Definition is accessible from the main entry point, should never be pruned.
12  Main,
13  /// Definition is not accessible from main, but is accessible from non-builtin definitions.
14  NonBuiltin,
15  /// Definition is not accessible from main, but is a user-defined constructor.
16  Ctr,
17}
18
19type Definitions = HashMap<Name, Used>;
20
21impl Ctx<'_> {
22  /// If `prune_all`, removes all unused definitions and adts starting from Main.
23  /// Otherwise, prunes only the builtins not accessible from any non-built-in definition.
24  ///
25  /// Emits unused definition warnings.
26  pub fn prune(&mut self, prune_all: bool) {
27    let mut used = Definitions::new();
28
29    // Get the functions that are accessible from the main entry point.
30    if let Some(main) = &self.book.entrypoint {
31      let def = self.book.defs.get(main).unwrap();
32      used.insert(main.clone(), Used::Main);
33      for rule in def.rules.iter() {
34        self.book.find_used_definitions_from_term(&rule.body, Used::Main, &mut used);
35      }
36    }
37
38    // Get the functions that are accessible from non-builtins.
39    for def in self.book.defs.values() {
40      if !def.is_builtin() && !(used.get(&def.name) == Some(&Used::Main)) {
41        if self.book.ctrs.contains_key(&def.name) {
42          used.insert(def.name.clone(), Used::Ctr);
43        } else {
44          used.insert(def.name.clone(), Used::NonBuiltin);
45        }
46        for rule in def.rules.iter() {
47          self.book.find_used_definitions_from_term(&rule.body, Used::NonBuiltin, &mut used);
48        }
49      }
50    }
51    for def in self.book.hvm_defs.values() {
52      if !def.source.is_builtin() && !(used.get(&def.name) == Some(&Used::Main)) {
53        used.insert(def.name.clone(), Used::NonBuiltin);
54        self.book.find_used_definitions_from_hvm_net(&def.body, Used::NonBuiltin, &mut used);
55      }
56    }
57
58    fn rm_def(book: &mut Book, def_name: &Name) {
59      if book.defs.contains_key(def_name) {
60        book.defs.shift_remove(def_name);
61      } else if book.hvm_defs.contains_key(def_name) {
62        book.hvm_defs.shift_remove(def_name);
63      } else {
64        unreachable!()
65      }
66    }
67
68    // Remove unused definitions.
69    let defs = self.book.defs.iter().map(|(nam, def)| (nam.clone(), def.source.clone()));
70    let hvm_defs = self.book.hvm_defs.iter().map(|(nam, def)| (nam.clone(), def.source.clone()));
71    let names = defs.chain(hvm_defs).collect::<Vec<_>>();
72
73    for (def, src) in names {
74      if let Some(use_) = used.get(&def) {
75        match use_ {
76          Used::Main => {
77            // Used by the main entry point, never pruned;
78          }
79          Used::NonBuiltin => {
80            // Used by a non-builtin definition.
81            // Prune if `prune_all`, otherwise show a warning.
82            if prune_all {
83              rm_def(self.book, &def);
84            } else if !def.is_generated() && !matches!(src.kind, SourceKind::Generated) {
85              self.info.add_function_warning(
86                "Definition is unused.",
87                WarningType::UnusedDefinition,
88                def,
89                src,
90              );
91            }
92          }
93          Used::Ctr => {
94            // Unused, but a user-defined constructor.
95            // Prune if `prune_all`, otherwise nothing.
96            if prune_all {
97              rm_def(self.book, &def);
98            } else {
99              // Don't show warning if it's a user-defined constructor.
100            }
101          }
102        }
103      } else {
104        // Unused builtin, can always be pruned.
105        rm_def(self.book, &def);
106      }
107    }
108  }
109}
110
111impl Book {
112  /// Finds all used definitions on the book, starting from the given term.
113  fn find_used_definitions_from_term(&self, term: &Term, used: Used, uses: &mut Definitions) {
114    maybe_grow(|| {
115      let mut to_find = vec![term];
116
117      while let Some(term) = to_find.pop() {
118        match term {
119          Term::Ref { nam: def_name } => self.insert_used(def_name, used, uses),
120          Term::List { .. } => {
121            self.insert_used(&Name::new(crate::fun::builtins::LCONS), used, uses);
122            self.insert_used(&Name::new(crate::fun::builtins::LNIL), used, uses);
123          }
124          Term::Str { .. } => {
125            self.insert_used(&Name::new(crate::fun::builtins::SCONS), used, uses);
126            self.insert_used(&Name::new(crate::fun::builtins::SNIL), used, uses);
127          }
128          _ => {}
129        }
130
131        for child in term.children() {
132          to_find.push(child);
133        }
134      }
135    })
136  }
137
138  fn find_used_definitions_from_hvm_net(&self, net: &Net, used: Used, uses: &mut Definitions) {
139    maybe_grow(|| {
140      let mut to_find = [&net.root]
141        .into_iter()
142        .chain(net.rbag.iter().flat_map(|(_, lft, rgt)| [lft, rgt]))
143        .collect::<Vec<_>>();
144
145      while let Some(term) = to_find.pop() {
146        match term {
147          Tree::Ref { nam } => self.insert_used(&Name::new(nam), used, uses),
148          Tree::Con { fst, snd }
149          | Tree::Dup { fst, snd }
150          | Tree::Opr { fst, snd }
151          | Tree::Swi { fst, snd } => {
152            to_find.push(fst);
153            to_find.push(snd);
154          }
155          Tree::Era | Tree::Var { .. } | Tree::Num { .. } => {}
156        }
157      }
158    })
159  }
160
161  fn insert_used(&self, def_name: &Name, used: Used, uses: &mut Definitions) {
162    if let Entry::Vacant(e) = uses.entry(def_name.clone()) {
163      e.insert(used);
164
165      // This needs to be done for each rule in case the pass it's
166      // ran from has not encoded the pattern match.
167      // E.g.: the `flatten_rules` golden test
168      if let Some(def) = self.defs.get(def_name) {
169        for rule in &def.rules {
170          self.find_used_definitions_from_term(&rule.body, used, uses);
171        }
172      } else if let Some(def) = self.hvm_defs.get(def_name) {
173        self.find_used_definitions_from_hvm_net(&def.body, used, uses);
174      } else {
175        unreachable!()
176      }
177    }
178  }
179}