WIP function inlining

This commit is contained in:
d0cd 2023-02-09 19:36:01 -08:00
parent b3ef6f79c3
commit c934bb35b3
5 changed files with 264 additions and 0 deletions

View File

@ -0,0 +1,37 @@
// Copyright (C) 2019-2023 Aleo Systems Inc.
// This file is part of the Leo library.
// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{Assigner, CallGraph, SymbolTable};
pub struct FunctionInliner<'a> {
/// The `SymbolTable` of the program.
pub(crate) symbol_table: &'a SymbolTable,
/// The call graph for the program.
pub(crate) call_graph: &'a CallGraph,
/// An struct used to construct (unique) variable names.
pub(crate) assigner: Assigner,
}
impl<'a> FunctionInliner<'a> {
/// Initializes a new `FunctionInliner`.
pub fn new(symbol_table: &'a SymbolTable, call_graph: &'a CallGraph, assigner: Assigner) -> Self {
Self {
symbol_table,
call_graph,
assigner,
}
}
}

View File

@ -0,0 +1,27 @@
// Copyright (C) 2019-2023 Aleo Systems Inc.
// This file is part of the Leo library.
// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::FunctionInliner;
use leo_ast::{CallExpression, Expression, ExpressionReconstructor, Statement};
impl ExpressionReconstructor for FunctionInliner<'_> {
type AdditionalOutput = Vec<Statement>;
fn reconstruct_call(&mut self, input: CallExpression) -> (Expression, Self::AdditionalOutput) {
todo!()
}
}

View File

@ -0,0 +1,59 @@
// Copyright (C) 2019-2023 Aleo Systems Inc.
// This file is part of the Leo library.
// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::FunctionInliner;
use indexmap::IndexMap;
use leo_ast::{Finalize, Function, ProgramReconstructor, ProgramScope, StatementReconstructor};
impl ProgramReconstructor for FunctionInliner<'_> {
fn reconstruct_program_scope(&mut self, input: ProgramScope) -> ProgramScope {
let mut reconstructed_functions = IndexMap::new();
// TODO: Reconstruct each of the functions in post-order and add them to the function map.
// TODO: Once implemented, we do not need to reorder functions during code generation.
ProgramScope {
program_id: input.program_id,
structs: input.structs,
mappings: input.mappings,
functions: reconstructed_functions,
span: input.span,
}
}
fn reconstruct_function(&mut self, input: Function) -> Function {
// TODO: Reconstruct the function in the correct order
Function {
annotations: input.annotations,
variant: input.variant,
identifier: input.identifier,
input: input.input,
output: input.output,
output_type: input.output_type,
block: self.reconstruct_block(input.block).0,
finalize: input.finalize.map(|finalize| Finalize {
identifier: finalize.identifier,
input: finalize.input,
output: finalize.output,
output_type: finalize.output_type,
block: self.reconstruct_block(finalize.block).0,
span: finalize.span,
}),
span: input.span,
}
}
}

View File

@ -0,0 +1,55 @@
// Copyright (C) 2019-2023 Aleo Systems Inc.
// This file is part of the Leo library.
// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::FunctionInliner;
use leo_ast::{Block, ConsoleStatement, DefinitionStatement, IterationStatement, Statement, StatementReconstructor};
impl StatementReconstructor for FunctionInliner<'_> {
/// Reconstructs the statements inside a basic block, accumulating any statements produced by function inlining.
fn reconstruct_block(&mut self, block: Block) -> (Block, Self::AdditionalOutput) {
let mut statements = Vec::with_capacity(block.statements.len());
for statement in block.statements {
let (reconstructed_statement, additional_statements) = self.reconstruct_statement(statement);
statements.extend(additional_statements);
statements.push(reconstructed_statement);
}
(
Block {
span: block.span,
statements,
},
Default::default(),
)
}
/// Parsing guarantees that console statements are not present in the program.
fn reconstruct_console(&mut self, _: ConsoleStatement) -> (Statement, Self::AdditionalOutput) {
unreachable!("`ConsoleStatement`s should not be in the AST at this phase of compilation.")
}
/// Static single assignment replaces definition statements with assignment statements.
fn reconstruct_definition(&mut self, _definition: DefinitionStatement) -> (Statement, Self::AdditionalOutput) {
unreachable!("`DefinitionStatement`s should not exist in the AST at this phase of compilation.")
}
/// Loop unrolling unrolls and removes iteration statements from the program.
fn reconstruct_iteration(&mut self, _input: IterationStatement) -> (Statement, Self::AdditionalOutput) {
unreachable!("`IterationStatement`s should not be in the AST at this phase of compilation.");
}
}

View File

@ -0,0 +1,86 @@
// Copyright (C) 2019-2023 Aleo Systems Inc.
// This file is part of the Leo library.
// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
//! The Function Inlining pass traverses the AST and inlines function at their call site.
//! See https://en.wikipedia.org/wiki/Inline_expansion for more information.
//! The pass also reorders `Function`s in a reconstructed `ProgramScope` so that they are in a post-order of the call graph.
//! In other words, a callee function will appear before a caller function in the order.
//!
//! Consider the following Leo code in SSA form.
//! ```leo
//! function main(flag: u8, value: u8) -> u8 {
//! $var$0 = flag == 0u8;
//! if ($var$0) {
//! $var$1 = foo(value);
//! value$2 = $var$1;
//! return value$2;
//! }
//! value$3 = $var$0 ? value$2 : value;
//! return value$3;
//! }
//!
//! inline foo(x: u8) -> u8 {
//! $var$4 = x * x;
//! return $var$4;
//! }
//! ```
//!
//! The inlining pass produces the following code.
//! ```leo
//! inline foo(x: u8) -> u8 {
//! $var$4 = x * x;
//! return $var$4;
//! }
//!
//! function main(flag: u8, value: u8) -> u8 {
//! $var$0 = flag == 0u8;
//! if ($var$0) {
//! $var$4$5 = value * value;
//! $var$1 = $var$4$5;
//! value$2 = $var$1;
//! return value$2;
//! }
//! value$3 = $var$0 ? value$2 : value;
//! return value$3;
//! }
//! ```
//! Note that the redundant assignments have no effect on the bytecode generated by the compiler.
mod inline_expression;
mod inline_statement;
mod inline_program;
pub mod function_inliner;
pub use function_inliner::*;
use crate::{Assigner, CallGraph, Pass, SymbolTable};
use leo_ast::{Ast, ProgramReconstructor};
use leo_errors::Result;
impl<'a> Pass for FunctionInliner<'a> {
type Input = (Ast, &'a SymbolTable, &'a CallGraph, Assigner);
type Output = Result<(Ast, Assigner)>;
fn do_pass((ast, st, call_graph, assigner): Self::Input) -> Self::Output {
let mut reconstructor = FunctionInliner::new(st, call_graph, assigner);
let program = reconstructor.reconstruct_program(ast.into_repr());
Ok((Ast::new(program), reconstructor.assigner))
}
}