Skip to content

Commit

Permalink
Refactored SymbolTable and related changes.
Browse files Browse the repository at this point in the history
The SymbolTable design is now a little simpler, and local scopes
don't have tables to store functions, structs, etc, since those
are only globals anyway.

Remove the symbol table json code.

Revise code catching assignments in conditionals in async functions.
Previously, code like this:

if x {
  let y: u8 = 1u8;
  {
    y = 2u8;
  }
}

would trigger the error, but it should actually pass.

Can use locators for local records and mappings

Remove the N type parameter in TypeChecker. Instead, we have a
`NetworkLimits` struct which contains the values the TypeChecker
will actually use. This cuts down on code bloat a bit.
  • Loading branch information
mikebenfield committed Jan 23, 2025
1 parent 51fd5dc commit 72a9acc
Show file tree
Hide file tree
Showing 711 changed files with 1,767 additions and 2,129 deletions.
43 changes: 4 additions & 39 deletions compiler/ast/src/common/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,52 +14,17 @@
// 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::CompositeType;
use leo_span::Symbol;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::{Deserialize, Serialize};

// Create custom struct to wrap (Symbol, Symbol) so that it can be serialized and deserialized.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Location {
pub program: Option<Symbol>,
pub program: Symbol,
pub name: Symbol,
}

impl Location {
// Create new Location instance.
pub fn new(program: Option<Symbol>, name: Symbol) -> Location {
pub fn new(program: Symbol, name: Symbol) -> Location {
Location { program, name }
}
}

impl From<&CompositeType> for Location {
fn from(composite: &CompositeType) -> Location {
Location::new(composite.program, composite.id.name)
}
}

impl Serialize for Location {
fn serialize<S>(&self, serializer: S) -> leo_errors::Result<S::Ok, S::Error>
where
S: Serializer,
{
let condensed_str = match self.program {
Some(program) => format!("{}/{}", program, self.name),
None => format!("{}", self.name),
};
serializer.serialize_str(&condensed_str)
}
}

impl<'de> Deserialize<'de> for Location {
fn deserialize<D>(deserializer: D) -> leo_errors::Result<Location, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let mut parts: Vec<&str> = s.split('/').collect();
let program = if parts.len() == 1 { None } else { Some(Symbol::intern(parts.remove(0))) };
let name = Symbol::intern(parts.first().unwrap());
Ok(Location::new(program, name))
}
}
4 changes: 2 additions & 2 deletions compiler/ast/src/stub/function_stub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ impl FunctionStub {
id: Default::default(),
type_: Type::Future(FutureType::new(
Vec::new(),
Some(Location::new(Some(program), Identifier::from(function.name()).name)),
Some(Location::new(program, Identifier::from(function.name()).name)),
false,
)),
}],
Expand Down Expand Up @@ -278,7 +278,7 @@ impl FunctionStub {
FutureFinalizeType(val) => Type::Future(FutureType::new(
Vec::new(),
Some(Location::new(
Some(Identifier::from(val.program_id().name()).name),
Identifier::from(val.program_id().name()).name,
Symbol::intern(&format!("finalize/{}", val.resource())),
)),
false,
Expand Down
33 changes: 5 additions & 28 deletions compiler/compiler/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ use crate::CompilerOptions;
pub use leo_ast::Ast;
use leo_ast::{NodeBuilder, Program, Stub};
use leo_errors::{CompilerError, Result, emitter::Handler};
pub use leo_passes::SymbolTable;
use leo_passes::*;
use leo_span::{Symbol, source_map::FileName, symbol::with_session_globals};

Expand Down Expand Up @@ -147,19 +146,17 @@ impl<'a, N: Network> Compiler<'a, N> {
/// Runs the symbol table pass.
pub fn symbol_table_pass(&self) -> Result<SymbolTable> {
let symbol_table = SymbolTableCreator::do_pass((&self.ast, self.handler))?;
if self.compiler_options.output.initial_symbol_table {
self.write_symbol_table_to_json("initial_symbol_table.json", &symbol_table)?;
}
Ok(symbol_table)
}

/// Runs the type checker pass.
pub fn type_checker_pass(&'a self, symbol_table: SymbolTable) -> Result<(SymbolTable, StructGraph, CallGraph)> {
let (symbol_table, struct_graph, call_graph) =
TypeChecker::<N>::do_pass((&self.ast, self.handler, symbol_table, &self.type_table))?;
if self.compiler_options.output.type_checked_symbol_table {
self.write_symbol_table_to_json("type_checked_symbol_table.json", &symbol_table)?;
}
TypeChecker::do_pass((&self.ast, self.handler, symbol_table, &self.type_table, NetworkLimits {
max_array_elements: N::MAX_ARRAY_ELEMENTS,
max_mappings: N::MAX_MAPPINGS,
max_functions: N::MAX_FUNCTIONS,
}))?;
Ok((symbol_table, struct_graph, call_graph))
}

Expand Down Expand Up @@ -190,10 +187,6 @@ impl<'a, N: Network> Compiler<'a, N> {
self.write_ast_to_json("unrolled_ast.json")?;
}

if self.compiler_options.output.unrolled_symbol_table {
self.write_symbol_table_to_json("unrolled_symbol_table.json", &symbol_table)?;
}

Ok(symbol_table)
}

Expand Down Expand Up @@ -340,22 +333,6 @@ impl<'a, N: Network> Compiler<'a, N> {
Ok(())
}

/// Writes the Symbol Table to a JSON file.
fn write_symbol_table_to_json(&self, file_suffix: &str, symbol_table: &SymbolTable) -> Result<()> {
// Remove `Span`s if they are not enabled.
if self.compiler_options.output.symbol_table_spans_enabled {
symbol_table
.to_json_file(self.output_directory.clone(), &format!("{}.{file_suffix}", self.program_name))?;
} else {
symbol_table.to_json_file_without_keys(
self.output_directory.clone(),
&format!("{}.{file_suffix}", self.program_name),
&["_span", "span"],
)?;
}
Ok(())
}

/// Merges the dependencies defined in `program.json` with the dependencies imported in `.leo` file
pub fn add_import_stubs(&mut self) -> Result<()> {
// Create a list of both the explicit dependencies specified in the `.leo` file, as well as the implicit ones derived from those dependencies.
Expand Down
8 changes: 0 additions & 8 deletions compiler/compiler/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,6 @@ pub struct BuildOptions {

#[derive(Clone, Default)]
pub struct OutputOptions {
//// Whether spans are enabled in the output symbol tables.
pub symbol_table_spans_enabled: bool,
// If enabled writes the symbol table after symbol table pass
pub initial_symbol_table: bool,
/// If enabled writes the symbol table after type checking.
pub type_checked_symbol_table: bool,
/// If enabled writes the symbol table after loop unrolling.
pub unrolled_symbol_table: bool,
/// Whether spans are enabled in the output ASTs.
pub ast_spans_enabled: bool,
/// If enabled writes the AST after parsing.
Expand Down
11 changes: 0 additions & 11 deletions compiler/compiler/tests/integration/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,6 @@ fn run_test(test: Test, handler: &Handler, buf: &BufferEmitter) -> Result<Value,
let compiler_options = CompilerOptions {
build,
output: OutputOptions {
symbol_table_spans_enabled: false,
initial_symbol_table: true,
type_checked_symbol_table: true,
unrolled_symbol_table: true,
ast_spans_enabled: false,
initial_ast: true,
unrolled_ast: true,
Expand Down Expand Up @@ -137,19 +133,12 @@ fn run_test(test: Test, handler: &Handler, buf: &BufferEmitter) -> Result<Value,
let (initial_ast, unrolled_ast, ssa_ast, flattened_ast, destructured_ast, inlined_ast, dce_ast) =
hash_asts(&program_name);

// Hash the symbol tables.
let (initial_symbol_table, type_checked_symbol_table, unrolled_symbol_table) =
hash_symbol_tables(&program_name);

// Clean up the output directory.
if fs::read_dir("/tmp/output").is_ok() {
fs::remove_dir_all(Path::new("/tmp/output")).expect("Error failed to clean up output dir.");
}

let output = CompileOutput {
initial_symbol_table,
type_checked_symbol_table,
unrolled_symbol_table,
initial_ast,
unrolled_ast,
ssa_ast,
Expand Down
11 changes: 0 additions & 11 deletions compiler/compiler/tests/integration/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,6 @@ fn run_test(test: Test, handler: &Handler, buf: &BufferEmitter) -> Result<Value,
let compiler_options = CompilerOptions {
build,
output: OutputOptions {
symbol_table_spans_enabled: false,
initial_symbol_table: true,
type_checked_symbol_table: true,
unrolled_symbol_table: true,
ast_spans_enabled: false,
initial_ast: true,
unrolled_ast: true,
Expand Down Expand Up @@ -179,19 +175,12 @@ fn run_test(test: Test, handler: &Handler, buf: &BufferEmitter) -> Result<Value,
let (initial_ast, unrolled_ast, ssa_ast, flattened_ast, destructured_ast, inlined_ast, dce_ast) =
hash_asts(&program_name);

// Hash the symbol tables.
let (initial_symbol_table, type_checked_symbol_table, unrolled_symbol_table) =
hash_symbol_tables(&program_name);

// Clean up the output directory.
if fs::read_dir("/tmp/output").is_ok() {
fs::remove_dir_all(Path::new("/tmp/output")).expect("Error failed to clean up output dir.");
}

let output = CompileOutput {
initial_symbol_table,
type_checked_symbol_table,
unrolled_symbol_table,
initial_ast,
unrolled_ast,
ssa_ast,
Expand Down
8 changes: 0 additions & 8 deletions compiler/compiler/tests/integration/utilities/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,6 @@ pub fn hash_asts(program_name: &str) -> (String, String, String, String, String,
(initial_ast, unrolled_ast, ssa_ast, flattened_ast, destructured_ast, inlined_ast, dce_ast)
}

pub fn hash_symbol_tables(program_name: &str) -> (String, String, String) {
let initial_symbol_table = hash_file(&format!("/tmp/output/{program_name}.initial_symbol_table.json"));
let type_checked_symbol_table = hash_file(&format!("/tmp/output/{program_name}.type_checked_symbol_table.json"));
let unrolled_symbol_table = hash_file(&format!("/tmp/output/{program_name}.unrolled_symbol_table.json"));

(initial_symbol_table, type_checked_symbol_table, unrolled_symbol_table)
}

pub fn get_cwd_option(test: &Test) -> Option<PathBuf> {
// Check for CWD option:
// ``` cwd: import ```
Expand Down
3 changes: 0 additions & 3 deletions compiler/compiler/tests/integration/utilities/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ use super::*;

#[derive(Deserialize, PartialEq, Eq, Serialize)]
pub struct CompileOutput {
pub initial_symbol_table: String,
pub type_checked_symbol_table: String,
pub unrolled_symbol_table: String,
pub initial_ast: String,
pub unrolled_ast: String,
pub ssa_ast: String,
Expand Down
48 changes: 26 additions & 22 deletions compiler/passes/src/code_generation/visit_expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,12 @@ impl<'a> CodeGenerator<'a> {
}

fn visit_locator(&mut self, input: &'a LocatorExpression) -> (String, String) {
(format!("{input}"), String::new())
if input.program.name.name == self.program_id.expect("Locators only appear within programs.").name.name {
// This locator refers to the current program, so we only output the name, not the program.
(format!("{}", input.name), String::new())
} else {
(format!("{input}"), String::new())
}
}

fn visit_binary(&mut self, input: &'a BinaryExpression) -> (String, String) {
Expand Down Expand Up @@ -518,28 +523,28 @@ impl<'a> CodeGenerator<'a> {
_ => unreachable!("Parsing guarantees that a function name is always an identifier."),
};

// Check if function is external.
let main_program = input.program.unwrap();
let caller_program = self.program_id.expect("Calls only appear within programs.").name.name;
let callee_program = input.program.unwrap_or(caller_program);

let func_symbol = self
.symbol_table
.lookup_function(Location::new(callee_program, function_name))
.expect("Type checking guarantees functions exist");

// Need to determine the program the function originated from as well as if the function has a finalize block.
let mut call_instruction = if main_program != self.program_id.unwrap().name.name {
let mut call_instruction = if caller_program != callee_program {
// All external functions must be defined as stubs.
if self.program.stubs.get(&main_program).is_some() {
format!(" call {}.aleo/{}", main_program, input.function)
} else {
unreachable!("Type checking guarantees that imported and stub programs are well defined.")
}
assert!(
self.program.stubs.get(&callee_program).is_some(),
"Type checking guarantees that imported and stub programs are present."
);
format!(" call {}.aleo/{}", callee_program, input.function)
} else if func_symbol.function.variant.is_async() {
format!(" async {}", self.current_function.unwrap().identifier)
} else {
// Lookup in symbol table to determine if its an async function.
if let Some(func) = self.symbol_table.lookup_fn_symbol(Location::new(input.program, function_name)) {
if func.variant.is_async() && input.program.unwrap() == self.program_id.unwrap().name.name {
format!(" async {}", self.current_function.unwrap().identifier)
} else {
format!(" call {}", input.function)
}
} else {
unreachable!("Type checking guarantees that all functions are well defined.")
}
format!(" call {}", input.function)
};

let mut instructions = String::new();

for argument in input.arguments.iter() {
Expand All @@ -552,8 +557,7 @@ impl<'a> CodeGenerator<'a> {
let mut destinations = Vec::new();

// Create operands for the output registers.
let func = &self.symbol_table.lookup_fn_symbol(Location::new(Some(main_program), function_name)).unwrap();
match func.output_type.clone() {
match func_symbol.function.output_type.clone() {
Type::Unit => {} // Do nothing
Type::Tuple(tuple) => match tuple.length() {
0 | 1 => unreachable!("Parsing guarantees that a tuple type has at least two elements"),
Expand All @@ -573,7 +577,7 @@ impl<'a> CodeGenerator<'a> {
}

// Add a register for async functions to represent the future created.
if func.variant == Variant::AsyncFunction {
if func_symbol.function.variant == Variant::AsyncFunction {
let destination_register = format!("r{}", self.next_register);
destinations.push(destination_register);
self.next_register += 1;
Expand Down
39 changes: 13 additions & 26 deletions compiler/passes/src/code_generation/visit_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,17 @@ impl<'a> CodeGenerator<'a> {
// Note that the unwrap is safe since type checking guarantees that the struct dependency graph is acyclic.
let order = self.struct_graph.post_order().unwrap();

// Create a mapping of symbols to references of structs so can perform constant-time lookups.
let structs_map: IndexMap<Symbol, &Composite> = self
.symbol_table
.structs
.iter()
.filter_map(|(name, struct_)| {
// Only include structs and local records.
if !(struct_.is_record
&& struct_.external.map(|program| program != self.program_id.unwrap().name.name).unwrap_or(false))
{
Some((name.name, struct_))
} else {
None
}
})
.collect();
let this_program = self.program_id.unwrap().name.name;

let lookup = |name: Symbol| {
self.symbol_table
.lookup_struct(name)
.or_else(|| self.symbol_table.lookup_record(Location::new(this_program, name)))
};

// Visit each `Struct` or `Record` in the post-ordering and produce an Aleo struct or record.
for name in order.into_iter() {
if let Some(struct_) = structs_map.get(&name) {
if let Some(struct_) = lookup(name) {
program_string.push_str(&self.visit_struct_or_record(struct_));
}
}
Expand All @@ -90,13 +81,10 @@ impl<'a> CodeGenerator<'a> {
// Generate code for the associated finalize function.
let finalize = &self
.symbol_table
.lookup_fn_symbol(Location::new(
Some(self.program_id.unwrap().name.name),
function.identifier.name,
))
.lookup_function(Location::new(self.program_id.unwrap().name.name, function.identifier.name))
.unwrap()
.clone()
.finalize
.finalizer
.unwrap();
// Write the finalize string.
function_string.push_str(&self.visit_function_with(
Expand Down Expand Up @@ -205,11 +193,10 @@ impl<'a> CodeGenerator<'a> {
};
// Futures are displayed differently in the input section. `input r0 as foo.aleo/bar.future;`
if matches!(input.type_, Type::Future(_)) {
let location = futures
let location = *futures
.next()
.expect("Type checking guarantees we have future locations for each future input")
.clone();
format!("{}.aleo/{}.future", location.program.unwrap(), location.name)
.expect("Type checking guarantees we have future locations for each future input");
format!("{}.aleo/{}.future", location.program, location.name)
} else {
self.visit_type_with_visibility(&input.type_, visibility)
}
Expand Down
Loading

0 comments on commit 72a9acc

Please sign in to comment.