Skip to content

Commit ee07305

Browse files
mohammadfawazMohammad Fawaz
andauthored
Functions in libraries (#29234)
Co-authored-by: Mohammad Fawaz <mohammadfawaz@gmail.com>
1 parent 306fc82 commit ee07305

File tree

101 files changed

+1817
-67
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+1817
-67
lines changed

crates/ast/src/library.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,22 @@
1616

1717
use leo_span::Symbol;
1818

19-
use crate::{Composite, ConstDeclaration, Indent};
19+
use crate::{Composite, ConstDeclaration, Function, Indent};
2020
use serde::{Deserialize, Serialize};
2121
use std::fmt;
2222

2323
/// Stores the Leo library abstract syntax tree.
2424
///
25-
/// Libraries may contain `const` declarations and `struct` definitions.
25+
/// Libraries may contain `const` declarations, `struct` definitions, and `fn` functions.
2626
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
2727
pub struct Library {
2828
pub name: Symbol,
2929
/// The constants defined in this library.
3030
pub consts: Vec<(Symbol, ConstDeclaration)>,
3131
/// The struct definitions in this library.
3232
pub structs: Vec<(Symbol, Composite)>,
33+
/// The function definitions in this library.
34+
pub functions: Vec<(Symbol, Function)>,
3335
}
3436

3537
impl fmt::Display for Library {
@@ -40,6 +42,10 @@ impl fmt::Display for Library {
4042
writeln!(f, "{}", Indent(struct_def))?;
4143
}
4244

45+
for (_, func) in self.functions.iter() {
46+
writeln!(f, "{}", Indent(func))?;
47+
}
48+
4349
for (_, const_decl) in self.consts.iter() {
4450
writeln!(f, "{};", Indent(const_decl))?;
4551
}

crates/ast/src/passes/reconstructor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ pub trait ProgramReconstructor: AstReconstructor {
607607
})
608608
.collect(),
609609
structs: input.structs.into_iter().map(|(i, s)| (i, self.reconstruct_composite(s))).collect(),
610+
functions: input.functions.into_iter().map(|(i, f)| (i, self.reconstruct_function(f))).collect(),
610611
}
611612
}
612613

crates/ast/src/passes/visitor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ pub trait ProgramVisitor: AstVisitor {
345345
fn visit_library(&mut self, input: &Library) {
346346
input.consts.iter().for_each(|(_, c)| self.visit_const(c));
347347
input.structs.iter().for_each(|(_, s)| self.visit_composite(s));
348+
input.functions.iter().for_each(|(_, f)| self.visit_function(f));
348349
}
349350

350351
fn visit_stub(&mut self, input: &Stub) {

crates/parser/src/rowan.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,6 +1802,7 @@ impl<'a> ConversionContext<'a> {
18021802
item: &SyntaxNode,
18031803
consts: &mut Vec<(Symbol, leo_ast::ConstDeclaration)>,
18041804
structs: &mut Vec<(Symbol, leo_ast::Composite)>,
1805+
functions: &mut Vec<(Symbol, leo_ast::Function)>,
18051806
) -> Result<()> {
18061807
if is_library_item(item.kind()) {
18071808
match item.kind() {
@@ -1813,14 +1814,18 @@ impl<'a> ConversionContext<'a> {
18131814
let composite = self.to_composite(item)?;
18141815
structs.push((composite.identifier.name, composite));
18151816
}
1816-
// Future library items (functions, etc.) will be handled here.
1817+
FUNCTION_DEF => {
1818+
// `is_in_program_block = false` so the variant is always `Fn` (not EntryPoint).
1819+
let func = self.to_function(item, false)?;
1820+
functions.push((func.identifier.name, func));
1821+
}
18171822
_ => {}
18181823
}
18191824
} else if is_program_item(item.kind()) {
18201825
// A recognized program-only item appeared in a library file.
18211826
let span = self.to_span(item);
18221827
self.handler.emit_err(ParserError::custom(
1823-
"Only `const` declarations and `struct` definitions are allowed in a library.",
1828+
"Only `const` declarations, `struct` definitions, and `fn` functions are allowed in a library.",
18241829
span,
18251830
));
18261831
}
@@ -1978,12 +1983,13 @@ impl<'a> ConversionContext<'a> {
19781983
fn to_library(&self, name: Symbol, node: &SyntaxNode) -> Result<leo_ast::Library> {
19791984
let mut consts = Vec::new();
19801985
let mut structs = Vec::new();
1986+
let mut functions = Vec::new();
19811987

19821988
for child in children(node) {
1983-
self.collect_library_item(&child, &mut consts, &mut structs)?;
1989+
self.collect_library_item(&child, &mut consts, &mut structs, &mut functions)?;
19841990
}
19851991

1986-
Ok(leo_ast::Library { name, consts, structs })
1992+
Ok(leo_ast::Library { name, consts, structs, functions })
19871993
}
19881994

19891995
/// Extract a ProgramId from an IMPORT node. Guarantees `network` is always present.
@@ -3055,7 +3061,7 @@ fn compute_module_key(name: &FileName, root_dir: Option<&std::path::Path>) -> Op
30553061
/// Currently `const` declarations and `struct` definitions are allowed; this list will grow
30563062
/// as library support expands to include functions and other items.
30573063
fn is_library_item(kind: SyntaxKind) -> bool {
3058-
matches!(kind, GLOBAL_CONST | STRUCT_DEF)
3064+
matches!(kind, GLOBAL_CONST | STRUCT_DEF | FUNCTION_DEF)
30593065
}
30603066

30613067
/// Returns `true` for syntax node kinds that are valid inside a program (`main.leo`).

crates/passes/src/common_subexpression_elimination/program.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,16 @@
1616

1717
use super::CommonSubexpressionEliminatingVisitor;
1818

19-
use leo_ast::{AstReconstructor, Constructor, Function, Module, ProgramReconstructor};
19+
use leo_ast::{AstReconstructor, Constructor, Function, Library, Module, ProgramReconstructor};
2020

2121
impl ProgramReconstructor for CommonSubexpressionEliminatingVisitor<'_> {
22+
fn reconstruct_library(&mut self, input: Library) -> Library {
23+
// Library function bodies are no longer relevant after function inlining and may not
24+
// satisfy CSE's invariants (e.g. expression type-table entries for raw function bodies).
25+
// Pass functions through unchanged.
26+
input
27+
}
28+
2229
fn reconstruct_program_scope(&mut self, mut input: leo_ast::ProgramScope) -> leo_ast::ProgramScope {
2330
input.functions = input.functions.into_iter().map(|(i, f)| (i, self.reconstruct_function(f))).collect();
2431
input.constructor = input.constructor.map(|c| self.reconstruct_constructor(c));

crates/passes/src/const_propagation/program.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ impl ProgramReconstructor for ConstPropagationVisitor<'_> {
9999
*c = declaration;
100100
}
101101

102+
// Skip generic functions (those with const parameters): they cannot be fully evaluated
103+
// until they are monomorphized into the consuming program's scope.
104+
input.functions = input
105+
.functions
106+
.into_iter()
107+
.map(|(i, f)| if f.const_parameters.is_empty() { (i, self.reconstruct_function(f)) } else { (i, f) })
108+
.collect();
109+
102110
input
103111
}
104112

crates/passes/src/flattening/program.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,27 @@ use leo_ast::{
2121
Constructor,
2222
Expression,
2323
Function,
24+
Library,
2425
ProgramReconstructor,
2526
ProgramScope,
2627
ReturnStatement,
2728
Statement,
2829
};
2930

3031
impl ProgramReconstructor for FlatteningVisitor<'_> {
32+
fn reconstruct_library(&mut self, input: Library) -> Library {
33+
let prev_program = self.program;
34+
self.program = input.name;
35+
let library = Library {
36+
name: input.name,
37+
consts: input.consts,
38+
structs: input.structs,
39+
functions: input.functions.into_iter().map(|(i, f)| (i, self.reconstruct_function(f))).collect(),
40+
};
41+
self.program = prev_program;
42+
library
43+
}
44+
3145
/// Flattens a program scope.
3246
fn reconstruct_program_scope(&mut self, input: ProgramScope) -> ProgramScope {
3347
self.program = input.program_id.as_symbol();

crates/passes/src/function_inlining/transform.rs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,11 @@ impl ProgramReconstructor for TransformVisitor<'_> {
7878
}
7979
}
8080

81-
// This is a sanity check to ensure that functions in the program scope have been processed.
81+
// Drop any library functions that were not reachable from this program (dead library code).
82+
// They are not needed and would fail the sanity check below.
83+
self.function_map.retain(|loc, _| !self.state.symbol_table.is_library(loc.program));
84+
85+
// This is a sanity check to ensure that all non-library functions in the program scope have been processed.
8286
assert!(self.function_map.is_empty(), "All functions in the program should have been processed.");
8387

8488
// Reconstruct the constructor.
@@ -87,11 +91,15 @@ impl ProgramReconstructor for TransformVisitor<'_> {
8791

8892
// Note that this intentionally clears `self.reconstructed_functions` for the next program scope.
8993
let functions = core::mem::take(&mut self.reconstructed_functions)
90-
.iter()
94+
.into_iter()
9195
.filter_map(|(loc, f)| {
92-
// Only consider functions defined at program scope. The rest are not relevant since they should all
93-
// have been inlined by now.
94-
loc.path.split_last().filter(|(_, rest)| rest.is_empty()).map(|(last, _)| (*last, f.clone()))
96+
// Only emit functions that belong to the current program scope and have a single-segment
97+
// path (no module prefix). Library functions and module-nested functions have all been
98+
// inlined at their call sites and must not appear as standalone functions in output.
99+
if loc.program != self.program {
100+
return None;
101+
}
102+
loc.path.split_last().filter(|(_, rest)| rest.is_empty()).map(|(last, _)| (*last, f))
95103
})
96104
.collect();
97105

@@ -150,8 +158,8 @@ impl ProgramReconstructor for TransformVisitor<'_> {
150158
}
151159

152160
fn reconstruct_program(&mut self, input: Program) -> Program {
153-
// Populate `self.function_map` using the functions in the program scopes and the modules
154-
// Use full Location (program + path) as keys to avoid collisions between programs
161+
// Populate `self.function_map` using the functions in the program scopes and the modules.
162+
// Use full Location (program + path) as keys to avoid collisions between programs.
155163
input
156164
.modules
157165
.iter()
@@ -169,6 +177,16 @@ impl ProgramReconstructor for TransformVisitor<'_> {
169177
self.function_map.insert(location, f);
170178
});
171179

180+
// Populate `function_map` with library functions from FromLibrary stubs.
181+
// Library functions are inlined at call sites just like module functions.
182+
input.stubs.iter().for_each(|(_, stub)| {
183+
if let Stub::FromLibrary { library, .. } = stub {
184+
library.functions.iter().for_each(|(name, f)| {
185+
self.function_map.entry(Location::new(library.name, vec![*name])).or_insert_with(|| f.clone());
186+
});
187+
}
188+
});
189+
172190
// Reconstruct program scopes. Inline functions defined in modules will be traversed
173191
// using the call graph and reconstructed in the right order.
174192
let program_scopes =
@@ -204,15 +222,17 @@ impl AstReconstructor for TransformVisitor<'_> {
204222

205223
/* Expressions */
206224
fn reconstruct_call(&mut self, input: CallExpression, _additional: &()) -> (Expression, Self::AdditionalOutput) {
207-
// Type checking guarantees that only functions local to the program scope can be inlined.
208-
if input.function.expect_global_location().program != self.program {
225+
let function_location = input.function.expect_global_location();
226+
227+
// Pass through calls to external programs (non-library). Library functions and module
228+
// functions are always inlined; only true cross-program calls are left as-is.
229+
if function_location.program != self.program && !self.state.symbol_table.is_library(function_location.program) {
209230
return (input.into(), Default::default());
210231
}
211232

212233
// Lookup the reconstructed callee function.
213234
// Since this pass processes functions in post-order, the callee function is guaranteed to exist in `self.reconstructed_functions`
214235
// Use full Location (program + path) to ensure correct lookup across multiple programs.
215-
let function_location = input.function.expect_global_location();
216236
let (_, callee) = self
217237
.reconstructed_functions
218238
.iter()
@@ -241,6 +261,10 @@ impl AstReconstructor for TransformVisitor<'_> {
241261
function_location.program == self.program && function_location.path.len() > 1,
242262
"this is a module function",
243263
) || match callee.variant {
264+
// Always inline library functions (they cannot exist as standalone Aleo functions).
265+
_ if self.state.symbol_table.is_library(function_location.program) => {
266+
mandatory_cond(true, "this is a library function")
267+
}
244268
Variant::FinalFn => mandatory_cond(true, "this is a final fn"),
245269
Variant::Fn => {
246270
mandatory_cond(

crates/passes/src/global_items_collection.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ impl ProgramVisitor for GlobalItemsCollectionVisitor<'_> {
207207

208208
input.structs.iter().for_each(|(_, s)| self.visit_composite(s));
209209
input.consts.iter().for_each(|(_, c)| self.visit_const(c));
210+
input.functions.iter().for_each(|(_, f)| self.visit_function(f));
210211
}
211212

212213
fn visit_aleo_program(&mut self, input: &AleoProgram) {

crates/passes/src/monomorphization/ast.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use leo_ast::{
2626
Expression,
2727
Identifier,
2828
Node as _,
29-
ProgramReconstructor,
29+
ProgramReconstructor as _,
3030
Type,
3131
};
3232

@@ -121,8 +121,12 @@ impl AstReconstructor for MonomorphizationVisitor<'_> {
121121
input_call: CallExpression,
122122
_additional: &(),
123123
) -> (Expression, Self::AdditionalOutput) {
124-
// Skip calls to functions from other programs.
125-
if input_call.function.expect_global_location().program != self.program {
124+
let callee_loc = input_call.function.expect_global_location();
125+
let callee_program = callee_loc.program;
126+
127+
// Skip calls to external programs that are not libraries (cross-program calls).
128+
// Library functions and current-program functions (including modules) proceed below.
129+
if callee_program != self.program && !self.state.symbol_table.is_library(callee_program) {
126130
return (input_call.into(), Default::default());
127131
}
128132

0 commit comments

Comments
 (0)