diff --git a/compiler/noirc_evaluator/src/ssa/opt/normalize_value_ids.rs b/compiler/noirc_evaluator/src/ssa/opt/normalize_value_ids.rs index b248f6734a9..ed57118b840 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/normalize_value_ids.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/normalize_value_ids.rs @@ -25,7 +25,7 @@ impl Ssa { let mut context = Context::default(); context.populate_functions(&self.functions); for function in self.functions.values_mut() { - context.normalize_ids(function); + context.normalize_ids(function, &self.globals); } self.functions = context.functions.into_btree(); } @@ -65,14 +65,14 @@ impl Context { } } - fn normalize_ids(&mut self, old_function: &mut Function) { + fn normalize_ids(&mut self, old_function: &mut Function, globals: &Function) { self.new_ids.blocks.clear(); self.new_ids.values.clear(); let new_function_id = self.new_ids.function_ids[&old_function.id()]; let new_function = &mut self.functions[new_function_id]; - for (_, value) in old_function.dfg.globals.values_iter() { + for (_, value) in globals.dfg.values_iter() { new_function.dfg.make_global(value.get_type().into_owned()); } diff --git a/compiler/noirc_evaluator/src/ssa/parser/ast.rs b/compiler/noirc_evaluator/src/ssa/parser/ast.rs index 6c7608a2f16..05743ffd7ca 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/ast.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/ast.rs @@ -7,9 +7,28 @@ use crate::ssa::ir::{function::RuntimeType, instruction::BinaryOp, types::Type}; #[derive(Debug)] pub(crate) struct ParsedSsa { + pub(crate) globals: Vec, pub(crate) functions: Vec, } +#[derive(Debug)] +pub(crate) struct ParsedGlobal { + pub(crate) name: Identifier, + pub(crate) value: ParsedGlobalValue, +} + +#[derive(Debug)] +pub(crate) enum ParsedGlobalValue { + NumericConstant(ParsedNumericConstant), + MakeArray(ParsedMakeArray), +} + +#[derive(Debug)] +pub(crate) struct ParsedMakeArray { + pub(crate) elements: Vec, + pub(crate) typ: Type, +} + #[derive(Debug)] pub(crate) struct ParsedFunction { pub(crate) runtime_type: RuntimeType, @@ -145,6 +164,12 @@ pub(crate) enum ParsedTerminator { #[derive(Debug, Clone)] pub(crate) enum ParsedValue { - NumericConstant { constant: FieldElement, typ: Type }, + NumericConstant(ParsedNumericConstant), Variable(Identifier), } + +#[derive(Debug, Clone)] +pub(crate) struct ParsedNumericConstant { + pub(crate) value: FieldElement, + pub(crate) typ: Type, +} diff --git a/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs b/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs index e2eea234dc7..550f642f65c 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use acvm::acir::circuit::ErrorSelector; @@ -6,15 +6,17 @@ use crate::ssa::{ function_builder::FunctionBuilder, ir::{ basic_block::BasicBlockId, + call_stack::CallStackId, + dfg::GlobalsGraph, function::{Function, FunctionId}, - instruction::ConstrainError, + instruction::{ConstrainError, Instruction}, value::ValueId, }, }; use super::{ - ast::AssertMessage, Identifier, ParsedBlock, ParsedFunction, ParsedInstruction, ParsedSsa, - ParsedTerminator, ParsedValue, RuntimeType, Ssa, SsaError, + ast::AssertMessage, Identifier, ParsedBlock, ParsedFunction, ParsedGlobal, ParsedGlobalValue, + ParsedInstruction, ParsedSsa, ParsedTerminator, ParsedValue, RuntimeType, Ssa, SsaError, Type, }; impl ParsedSsa { @@ -39,6 +41,17 @@ struct Translator { /// will recreate the SSA step by step, which can result in a new ID layout. variables: HashMap>, + /// The function that will hold the actual SSA globals. + globals_function: Function, + + /// The types of globals in the parsed SSA, in the order they were defined. + global_types: Vec, + + /// Maps names (e.g. "g0") in the parsed SSA to global IDs. + global_values: HashMap, + + globals_graph: Arc, + error_selector_counter: u64, } @@ -74,13 +87,26 @@ impl Translator { functions.insert(function.internal_name.clone(), function_id); } + // Does not matter what ID we use here. + let globals = Function::new("globals".to_owned(), main_id); + let mut translator = Self { builder, functions, variables: HashMap::new(), blocks: HashMap::new(), + globals_function: globals, + global_types: Vec::new(), + global_values: HashMap::new(), + globals_graph: Arc::new(GlobalsGraph::default()), error_selector_counter: 0, }; + + translator.translate_globals(std::mem::take(&mut parsed_ssa.globals))?; + + translator.globals_graph = + Arc::new(GlobalsGraph::from_dfg(translator.globals_function.dfg.clone())); + translator.translate_function_body(main_function)?; Ok(translator) @@ -103,6 +129,12 @@ impl Translator { } fn translate_function_body(&mut self, function: ParsedFunction) -> Result<(), SsaError> { + self.builder.current_function.set_globals(self.globals_graph.clone()); + + for typ in &self.global_types { + self.builder.current_function.dfg.make_global(typ.clone()); + } + // First define all blocks so that they are known (a block might jump to a block that comes next) for (index, block) in function.blocks.iter().enumerate() { // The first block is the entry block and it was automatically created by the builder @@ -297,8 +329,8 @@ impl Translator { fn translate_value(&mut self, value: ParsedValue) -> Result { match value { - ParsedValue::NumericConstant { constant, typ } => { - Ok(self.builder.numeric_constant(constant, typ.unwrap_numeric())) + ParsedValue::NumericConstant(constant) => { + Ok(self.builder.numeric_constant(constant.value, constant.typ.unwrap_numeric())) } ParsedValue::Variable(identifier) => self.lookup_variable(&identifier).or_else(|e| { self.lookup_function(&identifier) @@ -311,6 +343,45 @@ impl Translator { } } + fn translate_globals(&mut self, globals: Vec) -> Result<(), SsaError> { + for global in globals { + self.translate_global(global)?; + } + Ok(()) + } + + fn translate_global(&mut self, global: ParsedGlobal) -> Result<(), SsaError> { + let value_id = match global.value { + ParsedGlobalValue::NumericConstant(constant) => self + .globals_function + .dfg + .make_constant(constant.value, constant.typ.unwrap_numeric()), + ParsedGlobalValue::MakeArray(make_array) => { + let mut elements = im::Vector::new(); + for element in make_array.elements { + let element_id = match element { + ParsedValue::NumericConstant(constant) => self + .globals_function + .dfg + .make_constant(constant.value, constant.typ.unwrap_numeric()), + ParsedValue::Variable(identifier) => self.lookup_global(identifier)?, + }; + elements.push_back(element_id); + } + + let instruction = Instruction::MakeArray { elements, typ: make_array.typ.clone() }; + let block = self.globals_function.entry_block(); + let call_stack = CallStackId::root(); + self.globals_function + .dfg + .insert_instruction_and_results(instruction, block, None, call_stack) + .first() + } + }; + + self.define_global(global.name, value_id) + } + fn define_variable( &mut self, identifier: Identifier, @@ -329,13 +400,40 @@ impl Translator { } fn lookup_variable(&mut self, identifier: &Identifier) -> Result { - if let Some(value_id) = self.variables[&self.current_function_id()].get(&identifier.name) { + if let Some(value_id) = self + .variables + .get(&self.current_function_id()) + .and_then(|hash| hash.get(&identifier.name)) + { + Ok(*value_id) + } else if let Some(value_id) = self.global_values.get(&identifier.name) { Ok(*value_id) } else { Err(SsaError::UnknownVariable(identifier.clone())) } } + fn define_global(&mut self, identifier: Identifier, value_id: ValueId) -> Result<(), SsaError> { + if self.global_values.contains_key(&identifier.name) { + return Err(SsaError::GlobalAlreadyDefined(identifier)); + } + + self.global_values.insert(identifier.name, value_id); + + let typ = self.globals_function.dfg.type_of_value(value_id); + self.global_types.push(typ); + + Ok(()) + } + + fn lookup_global(&mut self, identifier: Identifier) -> Result { + if let Some(value_id) = self.global_values.get(&identifier.name) { + Ok(*value_id) + } else { + Err(SsaError::UnknownGlobal(identifier)) + } + } + fn lookup_block(&mut self, identifier: &Identifier) -> Result { if let Some(block_id) = self.blocks[&self.current_function_id()].get(&identifier.name) { Ok(*block_id) @@ -354,13 +452,14 @@ impl Translator { fn finish(self) -> Ssa { let mut ssa = self.builder.finish(); + ssa.globals = self.globals_function; + // Normalize the IDs so we have a better chance of matching the SSA we parsed // after the step-by-step reconstruction done during translation. This assumes // that the SSA we parsed was printed by the `SsaBuilder`, which normalizes // before each print. ssa.normalize_ids(); - // Does not matter what ID we use here. - ssa.globals = Function::new("globals".to_owned(), ssa.main_id); + ssa } diff --git a/compiler/noirc_evaluator/src/ssa/parser/mod.rs b/compiler/noirc_evaluator/src/ssa/parser/mod.rs index 143ba511879..cc660355bbd 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/mod.rs @@ -13,8 +13,9 @@ use super::{ use acvm::{AcirField, FieldElement}; use ast::{ - AssertMessage, Identifier, ParsedBlock, ParsedFunction, ParsedInstruction, ParsedParameter, - ParsedSsa, ParsedValue, + AssertMessage, Identifier, ParsedBlock, ParsedFunction, ParsedGlobal, ParsedGlobalValue, + ParsedInstruction, ParsedMakeArray, ParsedNumericConstant, ParsedParameter, ParsedSsa, + ParsedValue, }; use lexer::{Lexer, LexerError}; use noirc_errors::Span; @@ -99,6 +100,8 @@ pub(crate) enum SsaError { ParserError(ParserError), #[error("Unknown variable '{0}'")] UnknownVariable(Identifier), + #[error("Unknown global '{0}'")] + UnknownGlobal(Identifier), #[error("Unknown block '{0}'")] UnknownBlock(Identifier), #[error("Unknown function '{0}'")] @@ -107,6 +110,8 @@ pub(crate) enum SsaError { MismatchedReturnValues { returns: Vec, expected: usize }, #[error("Variable '{0}' already defined")] VariableAlreadyDefined(Identifier), + #[error("Global '{0}' already defined")] + GlobalAlreadyDefined(Identifier), } impl SsaError { @@ -114,8 +119,10 @@ impl SsaError { match self { SsaError::ParserError(parser_error) => parser_error.span(), SsaError::UnknownVariable(identifier) + | SsaError::UnknownGlobal(identifier) | SsaError::UnknownBlock(identifier) | SsaError::VariableAlreadyDefined(identifier) + | SsaError::GlobalAlreadyDefined(identifier) | SsaError::UnknownFunction(identifier) => identifier.span, SsaError::MismatchedReturnValues { returns, expected: _ } => returns[0].span, } @@ -138,12 +145,39 @@ impl<'a> Parser<'a> { } pub(crate) fn parse_ssa(&mut self) -> ParseResult { + let globals = self.parse_globals()?; + let mut functions = Vec::new(); while !self.at(Token::Eof) { let function = self.parse_function()?; functions.push(function); } - Ok(ParsedSsa { functions }) + Ok(ParsedSsa { globals, functions }) + } + + fn parse_globals(&mut self) -> ParseResult> { + let mut globals = Vec::new(); + + while let Some(name) = self.eat_identifier()? { + self.eat_or_error(Token::Assign)?; + + let value = self.parse_global_value()?; + globals.push(ParsedGlobal { name, value }); + } + + Ok(globals) + } + + fn parse_global_value(&mut self) -> ParseResult { + if let Some(constant) = self.parse_numeric_constant()? { + return Ok(ParsedGlobalValue::NumericConstant(constant)); + } + + if let Some(make_array) = self.parse_make_array()? { + return Ok(ParsedGlobalValue::MakeArray(make_array)); + } + + self.expected_global_value() } fn parse_function(&mut self) -> ParseResult { @@ -461,40 +495,12 @@ impl<'a> Parser<'a> { return Ok(ParsedInstruction::Load { target, value, typ }); } - if self.eat_keyword(Keyword::MakeArray)? { - if self.eat(Token::Ampersand)? { - let Some(string) = self.eat_byte_str()? else { - return self.expected_byte_string(); - }; - let u8 = Type::Numeric(NumericType::Unsigned { bit_size: 8 }); - let typ = Type::Slice(Arc::new(vec![u8.clone()])); - let elements = string - .bytes() - .map(|byte| ParsedValue::NumericConstant { - constant: FieldElement::from(byte as u128), - typ: u8.clone(), - }) - .collect(); - return Ok(ParsedInstruction::MakeArray { target, elements, typ }); - } else if let Some(string) = self.eat_byte_str()? { - let u8 = Type::Numeric(NumericType::Unsigned { bit_size: 8 }); - let typ = Type::Array(Arc::new(vec![u8.clone()]), string.len() as u32); - let elements = string - .bytes() - .map(|byte| ParsedValue::NumericConstant { - constant: FieldElement::from(byte as u128), - typ: u8.clone(), - }) - .collect(); - return Ok(ParsedInstruction::MakeArray { target, elements, typ }); - } else { - self.eat_or_error(Token::LeftBracket)?; - let elements = self.parse_comma_separated_values()?; - self.eat_or_error(Token::RightBracket)?; - self.eat_or_error(Token::Colon)?; - let typ = self.parse_type()?; - return Ok(ParsedInstruction::MakeArray { target, elements, typ }); - } + if let Some(make_array) = self.parse_make_array()? { + return Ok(ParsedInstruction::MakeArray { + target, + elements: make_array.elements, + typ: make_array.typ, + }); } if self.eat_keyword(Keyword::Not)? { @@ -524,6 +530,52 @@ impl<'a> Parser<'a> { self.expected_instruction_or_terminator() } + fn parse_make_array(&mut self) -> ParseResult> { + if !self.eat_keyword(Keyword::MakeArray)? { + return Ok(None); + } + + let make_array = if self.eat(Token::Ampersand)? { + let Some(string) = self.eat_byte_str()? else { + return self.expected_byte_string(); + }; + let u8 = Type::Numeric(NumericType::Unsigned { bit_size: 8 }); + let typ = Type::Slice(Arc::new(vec![u8.clone()])); + let elements = string + .bytes() + .map(|byte| { + ParsedValue::NumericConstant(ParsedNumericConstant { + value: FieldElement::from(byte as u128), + typ: u8.clone(), + }) + }) + .collect(); + ParsedMakeArray { elements, typ } + } else if let Some(string) = self.eat_byte_str()? { + let u8 = Type::Numeric(NumericType::Unsigned { bit_size: 8 }); + let typ = Type::Array(Arc::new(vec![u8.clone()]), string.len() as u32); + let elements = string + .bytes() + .map(|byte| { + ParsedValue::NumericConstant(ParsedNumericConstant { + value: FieldElement::from(byte as u128), + typ: u8.clone(), + }) + }) + .collect(); + ParsedMakeArray { elements, typ } + } else { + self.eat_or_error(Token::LeftBracket)?; + let elements = self.parse_comma_separated_values()?; + self.eat_or_error(Token::RightBracket)?; + self.eat_or_error(Token::Colon)?; + let typ = self.parse_type()?; + ParsedMakeArray { elements, typ } + }; + + Ok(Some(make_array)) + } + fn parse_terminator(&mut self) -> ParseResult { if let Some(terminator) = self.parse_return()? { return Ok(terminator); @@ -617,12 +669,8 @@ impl<'a> Parser<'a> { } fn parse_value(&mut self) -> ParseResult> { - if let Some(value) = self.parse_field_value()? { - return Ok(Some(value)); - } - - if let Some(value) = self.parse_int_value()? { - return Ok(Some(value)); + if let Some(constant) = self.parse_numeric_constant()? { + return Ok(Some(ParsedValue::NumericConstant(constant))); } if let Some(identifier) = self.eat_identifier()? { @@ -632,23 +680,35 @@ impl<'a> Parser<'a> { Ok(None) } - fn parse_field_value(&mut self) -> ParseResult> { + fn parse_numeric_constant(&mut self) -> ParseResult> { + if let Some(constant) = self.parse_field_value()? { + return Ok(Some(constant)); + } + + if let Some(constant) = self.parse_int_value()? { + return Ok(Some(constant)); + } + + Ok(None) + } + + fn parse_field_value(&mut self) -> ParseResult> { if self.eat_keyword(Keyword::Field)? { - let constant = self.eat_int_or_error()?; - Ok(Some(ParsedValue::NumericConstant { constant, typ: Type::field() })) + let value = self.eat_int_or_error()?; + Ok(Some(ParsedNumericConstant { value, typ: Type::field() })) } else { Ok(None) } } - fn parse_int_value(&mut self) -> ParseResult> { + fn parse_int_value(&mut self) -> ParseResult> { if let Some(int_type) = self.eat_int_type()? { - let constant = self.eat_int_or_error()?; + let value = self.eat_int_or_error()?; let typ = match int_type { IntType::Unsigned(bit_size) => Type::unsigned(bit_size), IntType::Signed(bit_size) => Type::signed(bit_size), }; - Ok(Some(ParsedValue::NumericConstant { constant, typ })) + Ok(Some(ParsedNumericConstant { value, typ })) } else { Ok(None) } @@ -932,6 +992,13 @@ impl<'a> Parser<'a> { }) } + fn expected_global_value(&mut self) -> ParseResult { + Err(ParserError::ExpectedGlobalValue { + found: self.token.token().clone(), + span: self.token.to_span(), + }) + } + fn expected_token(&mut self, token: Token) -> ParseResult { Err(ParserError::ExpectedToken { token, @@ -971,6 +1038,10 @@ pub(crate) enum ParserError { ExpectedByteString { found: Token, span: Span }, #[error("Expected a value, found '{found}'")] ExpectedValue { found: Token, span: Span }, + #[error( + "Expected a global value (Field literal, integer literal or make_array), found '{found}'" + )] + ExpectedGlobalValue { found: Token, span: Span }, #[error("Multiple return values only allowed for call")] MultipleReturnValuesOnlyAllowedForCall { second_target: Identifier }, } @@ -987,7 +1058,8 @@ impl ParserError { | ParserError::ExpectedInstructionOrTerminator { span, .. } | ParserError::ExpectedStringOrData { span, .. } | ParserError::ExpectedByteString { span, .. } - | ParserError::ExpectedValue { span, .. } => *span, + | ParserError::ExpectedValue { span, .. } + | ParserError::ExpectedGlobalValue { span, .. } => *span, ParserError::MultipleReturnValuesOnlyAllowedForCall { second_target, .. } => { second_target.span } diff --git a/compiler/noirc_evaluator/src/ssa/parser/tests.rs b/compiler/noirc_evaluator/src/ssa/parser/tests.rs index 8c24b2ec458..c803e2a94fe 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/tests.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/tests.rs @@ -530,3 +530,19 @@ fn test_does_not_simplify() { "; assert_ssa_roundtrip(src); } + +#[test] +fn parses_globals() { + let src = " + g0 = Field 0 + g1 = u32 1 + g2 = make_array [] : [Field; 0] + g3 = make_array [g2] : [[Field; 0]; 1] + + acir(inline) fn main f0 { + b0(): + return g3 + } + "; + assert_ssa_roundtrip(src); +}