Skip to main content

leo_passes/
library_pruning.rs

1// Copyright (C) 2019-2026 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17//! Drops library functions the program can never reach.
18//!
19//! `std` is injected into every program as a `FromLibrary` stub, so otherwise every compile drags
20//! all ~6,000 of its functions through the backend before dead-code elimination discards the
21//! unused ones. This pass removes library functions not transitively reachable from any
22//! non-library function, using the call graph that type checking just built.
23//!
24//! This complements [`crate::DeadCodeEliminating`], which drops uncalled functions within a
25//! program but is a no-op for libraries; library stubs are pruned here instead.
26
27use crate::{CompilerState, Pass};
28
29use leo_ast::{Ast, Library, Location, Stub};
30use leo_errors::Result;
31
32use indexmap::IndexSet;
33
34/// Pass that prunes unreachable functions from `FromLibrary` stubs.
35pub struct LibraryPruning;
36
37impl Pass for LibraryPruning {
38    type Input = ();
39    type Output = ();
40
41    const NAME: &str = "LibraryPruning";
42
43    fn do_pass(_input: Self::Input, state: &mut CompilerState) -> Result<Self::Output> {
44        // This pass runs in the program backend, which a library build skips (frontend only, no
45        // bytecode), so it never sees an `Ast::Library`. The guard skips programs that import no
46        // library (e.g. `no_std`); otherwise every `FromLibrary` stub below is pruned, std included.
47        let has_library_stub = matches!(&state.ast, Ast::Program(p)
48            if p.stubs.values().any(|s| matches!(s, Stub::FromLibrary { .. })));
49        if !has_library_stub {
50            return Ok(());
51        }
52
53        // Reachability via a single multi-source traversal from every non-library function. Every
54        // non-library function is a root, not just entry points, so a library function called from
55        // any imported program survives even when the caller is itself unreachable.
56        let mut reachable: IndexSet<Location> = IndexSet::new();
57        let mut queue: Vec<Location> = Vec::new();
58        for node in state.call_graph.nodes() {
59            if !state.symbol_table.is_library(node.program) {
60                queue.extend(state.call_graph.neighbors(node).cloned());
61            }
62        }
63        while let Some(node) = queue.pop() {
64            if reachable.insert(node.clone()) {
65                queue.extend(state.call_graph.neighbors(&node).cloned());
66            }
67        }
68
69        let Ast::Program(program) = &mut state.ast else { unreachable!("checked above") };
70        for stub in program.stubs.values_mut() {
71            if let Stub::FromLibrary { library, .. } = stub {
72                prune_library(library, &reachable);
73            }
74        }
75
76        Ok(())
77    }
78}
79
80/// Removes functions not in `reachable` from `library` and its submodules. Structs, consts, and
81/// interfaces are kept for now.
82fn prune_library(library: &mut Library, reachable: &IndexSet<Location>) {
83    let declares_const = !library.consts.is_empty() || library.modules.values().any(|module| !module.consts.is_empty());
84    if declares_const {
85        return;
86    }
87
88    let name = library.name;
89    library.functions.retain(|(symbol, _)| reachable.contains(&Location::new(name, vec![*symbol])));
90
91    for (path, module) in &mut library.modules {
92        let program = module.unit_name;
93        module.functions.retain(|(symbol, _)| {
94            let mut full_path = Vec::with_capacity(path.len() + 1);
95            full_path.extend_from_slice(path);
96            full_path.push(*symbol);
97            reachable.contains(&Location::new(program, full_path))
98        });
99    }
100}