Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Update] Supports new features for the upcoming network reset. #2626

Merged
merged 10 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
378 changes: 185 additions & 193 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion compiler/ast/src/program/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub struct Program {
/// A map from import names to import definitions.
pub imports: IndexMap<Symbol, (Program, Span)>,
/// A map from program names to program scopes.
pub program_scopes: IndexMap<ProgramId, ProgramScope>,
pub program_scopes: IndexMap<Symbol, ProgramScope>,
}

impl fmt::Display for Program {
Expand Down
2 changes: 1 addition & 1 deletion compiler/compiler/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ impl<'a> Compiler<'a> {
struct_graph: &StructGraph,
call_graph: &CallGraph,
) -> Result<String> {
CodeGenerator::do_pass((&self.ast, symbol_table, struct_graph, call_graph))
CodeGenerator::do_pass((&self.ast, symbol_table, struct_graph, call_graph, &self.ast.ast))
}

/// Runs the compiler stages.
Expand Down
14 changes: 8 additions & 6 deletions compiler/compiler/tests/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,17 @@ fn run_test(test: Test, handler: &Handler, err_buf: &BufferEmitter) -> Result<Va
.map(|input| console::program::Value::<Network>::from_str(input.as_str().unwrap()).unwrap())
.collect();
let input_string = format!("[{}]", inputs.iter().map(|input| input.to_string()).join(", "));
let private_key = match case.get(&Value::from("private_key")) {
Some(private_key) => {
PrivateKey::from_str(private_key.as_str().expect("expected string for private key"))
.expect("unable to parse private key")
}
None => dotenv_private_key(package.directory()).unwrap(),
};

// TODO: Add support for custom config like custom private keys.
// Execute the program and get the outputs.
let output_string = match package.run::<Aleo, _>(
&dotenv_private_key(package.directory()).unwrap(),
function_name,
&inputs,
rng,
) {
let output_string = match package.run::<Aleo, _>(&private_key, function_name, &inputs, rng) {
Ok((response, _)) => format!(
"[{}]",
response
Expand Down
2 changes: 1 addition & 1 deletion compiler/compiler/tests/utilities/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ pub fn compile_and_process<'a>(parsed: &'a mut Compiler<'a>) -> Result<String, L
parsed.dead_code_elimination_pass()?;

// Compile Leo program to bytecode.
let bytecode = CodeGenerator::do_pass((&parsed.ast, &st, &struct_graph, &call_graph))?;
let bytecode = CodeGenerator::do_pass((&parsed.ast, &st, &struct_graph, &call_graph, &parsed.ast.ast))?;

Ok(bytecode)
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/parser/src/parser/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl ParserContext<'_> {
false => {
parsed_program_scope = true;
let program_scope = self.parse_program_scope()?;
program_scopes.insert(program_scope.program_id, program_scope);
program_scopes.insert(program_scope.program_id.name.name, program_scope);
}
}
}
Expand Down
19 changes: 17 additions & 2 deletions compiler/passes/src/code_generation/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

use crate::{CallGraph, StructGraph, SymbolTable};

use leo_ast::Function;
use leo_ast::{Function, Program, ProgramId};
use leo_span::Symbol;

use indexmap::IndexMap;
Expand Down Expand Up @@ -44,11 +44,23 @@ pub struct CodeGenerator<'a> {
pub(crate) is_transition_function: bool,
/// Are we traversing a finalize block?
pub(crate) in_finalize: bool,
// TODO (@d0cd): There are a temporary solution to be compatible with futures introduced in Aleo instructions.
// The registers containing futures produced in the current transition.
pub(crate) futures: Vec<(String, String)>,
// A reference to program. This is needed to look up external programs.
pub(crate) program: &'a Program,
// The program ID of the current program.
pub(crate) program_id: Option<ProgramId>,
}

impl<'a> CodeGenerator<'a> {
/// Initializes a new `CodeGenerator`.
pub fn new(symbol_table: &'a SymbolTable, struct_graph: &'a StructGraph, _call_graph: &'a CallGraph) -> Self {
pub fn new(
symbol_table: &'a SymbolTable,
struct_graph: &'a StructGraph,
_call_graph: &'a CallGraph,
program: &'a Program,
) -> Self {
// Initialize variable mapping.
Self {
symbol_table,
Expand All @@ -61,6 +73,9 @@ impl<'a> CodeGenerator<'a> {
global_mapping: IndexMap::new(),
is_transition_function: false,
in_finalize: false,
futures: Vec::new(),
program,
program_id: None,
}
}
}
8 changes: 4 additions & 4 deletions compiler/passes/src/code_generation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ mod visit_type;

use crate::{CallGraph, Pass, StructGraph, SymbolTable};

use leo_ast::Ast;
use leo_ast::{Ast, Program};
use leo_errors::Result;

impl<'a> Pass for CodeGenerator<'a> {
type Input = (&'a Ast, &'a SymbolTable, &'a StructGraph, &'a CallGraph);
type Input = (&'a Ast, &'a SymbolTable, &'a StructGraph, &'a CallGraph, &'a Program);
type Output = Result<String>;

fn do_pass((ast, symbol_table, struct_graph, call_graph): Self::Input) -> Self::Output {
let mut generator = Self::new(symbol_table, struct_graph, call_graph);
fn do_pass((ast, symbol_table, struct_graph, call_graph, program): Self::Input) -> Self::Output {
let mut generator = Self::new(symbol_table, struct_graph, call_graph, program);
let bytecode = generator.visit_program(ast.as_repr());

Ok(bytecode)
Expand Down
86 changes: 66 additions & 20 deletions compiler/passes/src/code_generation/visit_expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,9 +471,36 @@ impl<'a> CodeGenerator<'a> {

// TODO: Cleanup
fn visit_call(&mut self, input: &'a CallExpression) -> (String, String) {
let mut call_instruction = match &input.external {
Some(external) => format!(" call {external}.aleo/{}", input.function),
None => format!(" call {}", input.function),
let (mut call_instruction, has_finalize) = match &input.external {
Some(external) => {
// If the function is an external call, then check whether or not it has an associated finalize block.
// Extract the program name from the external call.
let program_name = match **external {
Expression::Identifier(identifier) => identifier.name,
_ => unreachable!("Parsing guarantees that a program name is always an identifier."),
};
// Lookup the imported program scope.
let imported_program_scope = match self
.program
.imports
.get(&program_name)
.and_then(|(program, _)| program.program_scopes.get(&program_name))
{
Some(program) => program,
None => unreachable!("Type checking guarantees that imported programs are well defined."),
};
// Check if the external function has a finalize block.
let function_name = match *input.function {
Expression::Identifier(identifier) => identifier.name,
_ => unreachable!("Parsing guarantees that a function name is always an identifier."),
};
let has_finalize = match imported_program_scope.functions.get(&function_name) {
Some(function) => function.finalize.is_some(),
None => unreachable!("Type checking guarantees that imported functions are well defined."),
};
(format!(" call {external}.aleo/{}", input.function), has_finalize)
}
None => (format!(" call {}", input.function), false),
};
let mut instructions = String::new();

Expand All @@ -488,41 +515,60 @@ impl<'a> CodeGenerator<'a> {
Expression::Identifier(identifier) => identifier.name,
_ => unreachable!("Parsing guarantees that a function name is always an identifier."),
};

// Initialize storage for the destination registers.
let mut destinations = Vec::new();

let return_type = &self.symbol_table.lookup_fn_symbol(function_name).unwrap().output_type;
match return_type {
Type::Unit => {
call_instruction.push(';');
instructions.push_str(&call_instruction);
(String::new(), instructions)
} // Do nothing
Type::Unit => {} // Do nothing
Type::Tuple(tuple) => match tuple.len() {
0 | 1 => unreachable!("Parsing guarantees that a tuple type has at least two elements"),
len => {
let mut destinations = Vec::new();
for _ in 0..len {
let destination_register = format!("r{}", self.next_register);
destinations.push(destination_register);
self.next_register += 1;
}
let destinations = destinations.join(" ");
writeln!(call_instruction, " into {destinations};").expect("failed to write to string");
instructions.push_str(&call_instruction);

(destinations, instructions)
}
},
_ => {
// Push destination register to call instruction.
let destination_register = format!("r{}", self.next_register);
writeln!(call_instruction, " into {destination_register};").expect("failed to write to string");
instructions.push_str(&call_instruction);

// Increment the register counter.
destinations.push(destination_register);
self.next_register += 1;
}
}

(destination_register, instructions)
// If `has_finalize`, create another destination register for the future.
if has_finalize {
// Construct the future register.
let future_register = format!("r{}", self.next_register);
self.next_register += 1;

// Construct the future type.
let program_id = match input.external.as_deref() {
Some(Expression::Identifier(identifier)) => identifier,
_ => unreachable!("If `has_finalize` is true, then the external call must be an identifier."),
};
self.futures.push((future_register, format!("{program_id}/{function_name}")));
}

// If destination registers were created, write them to the call instruction.
if !destinations.is_empty() {
write!(call_instruction, " into").expect("failed to write to string");
for destination in &destinations {
write!(call_instruction, " {}", destination).expect("failed to write to string");
}
}

// Write the closing semicolon.
writeln!(call_instruction, ";").expect("failed to write to string");

// Push the call instruction to the list of instructions.
instructions.push_str(&call_instruction);

// Return the destination registers as a string and the instructions.
(destinations.join(" "), instructions)
}

fn visit_tuple(&mut self, input: &'a TupleExpression) -> (String, String) {
Expand Down
23 changes: 21 additions & 2 deletions compiler/passes/src/code_generation/visit_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ impl<'a> CodeGenerator<'a> {
// Note that type checking guarantees that there is exactly one program scope.
let program_scope: &ProgramScope = input.program_scopes.values().next().unwrap();

self.program_id = Some(program_scope.program_id);

// Print the program id.
writeln!(program_string, "program {};", program_scope.program_id)
.expect("Failed to write program id to string.");
Expand Down Expand Up @@ -157,6 +159,7 @@ impl<'a> CodeGenerator<'a> {
// Initialize the state of `self` with the appropriate values before visiting `function`.
self.next_register = 0;
self.variable_mapping = IndexMap::new();
self.futures.clear();
// TODO: Figure out a better way to initialize.
self.variable_mapping.insert(&sym::SelfLower, "self".to_string());
self.variable_mapping.insert(&sym::block, "block".to_string());
Expand Down Expand Up @@ -214,6 +217,17 @@ impl<'a> CodeGenerator<'a> {

function_string.push_str(&format!("\nfinalize {}:\n", finalize.identifier));

// If the function contained calls that produced futures, then we need to add the futures to the finalize block as input.
// Store the new future registers.
let mut future_registers = Vec::new();
for (_, future_type) in &self.futures {
let register_string = format!("r{}", self.next_register);
writeln!(function_string, " input {register_string} as {future_type};")
.expect("failed to write to string");
future_registers.push(register_string);
self.next_register += 1;
}

// Construct and append the input declarations of the finalize block.
for input in finalize.input.iter() {
let register_string = format!("r{}", self.next_register);
Expand All @@ -240,6 +254,11 @@ impl<'a> CodeGenerator<'a> {
.expect("failed to write to string");
}

// Invoke `await` on each future.
for register in future_registers {
writeln!(function_string, " await {register};").expect("failed to write to string");
}

// Construct and append the finalize block body.
function_string.push_str(&self.visit_block(&finalize.block));

Expand Down Expand Up @@ -273,10 +292,10 @@ impl<'a> CodeGenerator<'a> {
};

// Create the key string, e.g. ` key as address.public`.
mapping_string.push_str(&format!("\tkey left as {};\n", create_type(&mapping.key_type)));
mapping_string.push_str(&format!("\tkey as {};\n", create_type(&mapping.key_type)));

// Create the value string, e.g. ` value as address.public`.
mapping_string.push_str(&format!("\tvalue right as {};\n", create_type(&mapping.value_type)));
mapping_string.push_str(&format!("\tvalue as {};\n", create_type(&mapping.value_type)));

// Add the mapping to the variable mapping.
self.global_mapping.insert(&mapping.identifier.name, mapping.identifier.to_string());
Expand Down
52 changes: 39 additions & 13 deletions compiler/passes/src/code_generation/visit_statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl<'a> CodeGenerator<'a> {
}

fn visit_return(&mut self, input: &'a ReturnStatement) -> String {
let mut instructions = match input.expression {
let mut outputs = match input.expression {
// Skip empty return statements.
Expression::Unit(_) => String::new(),
_ => {
Expand Down Expand Up @@ -141,21 +141,47 @@ impl<'a> CodeGenerator<'a> {
}
};

// Output a finalize instruction if needed.
// TODO: Check formatting.
if let Some(arguments) = &input.finalize_arguments {
let mut finalize_instruction = "\n finalize".to_string();

for argument in arguments.iter() {
let (argument, argument_instructions) = self.visit_expression(argument);
write!(finalize_instruction, " {argument}").expect("failed to write to string");
instructions.push_str(&argument_instructions);
// Initialize storage for the instructions.
let mut instructions = String::new();

// If there are any futures or if the return instruction has `finalize_arguments`, then
// create an `async` instruction that uses them.
if !self.futures.is_empty() || input.finalize_arguments.is_some() {
// Note that this unwrap is safe, since `current_function` is set in `visit_function`.
let function_id = self.current_function.unwrap().name();
let mut async_instruction = format!(" async {function_id}");
// Add the futures to the async instruction.
for (future_register, _) in self.futures.iter() {
write!(async_instruction, " {}", future_register).expect("failed to write to string");
}
writeln!(finalize_instruction, ";").expect("failed to write to string");

instructions.push_str(&finalize_instruction);
// Add the finalize arguments to the async instruction.
if let Some(arguments) = &input.finalize_arguments {
for argument in arguments.iter() {
let (argument, argument_instructions) = self.visit_expression(argument);
write!(async_instruction, " {argument}").expect("failed to write to string");
instructions.push_str(&argument_instructions);
}
}
// Write the destination register.
let destination_register = format!("r{}", self.next_register);
write!(async_instruction, " into {};", destination_register).expect("failed to write to string");
// Increment the register counter.
self.next_register += 1;
// Add the async instruction to the instructions.
instructions.push_str(&async_instruction);

// Add the destination register to the outputs.
let program_id = match self.program_id {
Some(program_id) => program_id,
None => unreachable!("`program_id` should be set in `visit_function`"),
};
outputs
.push_str(&format!(" output {} as {}/{}.future;\n", destination_register, program_id, function_id));
}

// Extend the instructions with the outputs.
instructions.push_str(&outputs);

instructions
}

Expand Down
10 changes: 10 additions & 0 deletions compiler/passes/src/type_checking/check_expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ impl<'a> ExpressionVisitor<'a> for TypeChecker<'a> {
}
return Some(Type::Address);
}
sym::signer => {
// Check that operation is not invoked in a `finalize` block.
if self.is_finalize {
self.handler.emit_err(TypeCheckerError::invalid_operation_inside_finalize(
"self.signer",
access.name.span(),
))
}
return Some(Type::Address);
}
_ => {
self.emit_err(TypeCheckerError::invalid_self_access(access.name.span()));
}
Expand Down
1 change: 1 addition & 0 deletions compiler/span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ symbols! {
Return: "return",
SelfLower: "self",
SelfUpper: "Self",
signer,
Star: "*",
then,
transition,
Expand Down
Loading
Loading