From 213b6810d196c3158c364abf8fc2b5a3b3a46781 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 22 Nov 2024 06:56:48 -0300 Subject: [PATCH 1/3] fix: parse function types in SSA parser --- compiler/noirc_evaluator/src/ssa/parser/mod.rs | 4 ++++ compiler/noirc_evaluator/src/ssa/parser/tests.rs | 12 ++++++++++++ compiler/noirc_evaluator/src/ssa/parser/token.rs | 3 +++ 3 files changed, 19 insertions(+) diff --git a/compiler/noirc_evaluator/src/ssa/parser/mod.rs b/compiler/noirc_evaluator/src/ssa/parser/mod.rs index 7753908b2bd..b54fe73b8df 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/mod.rs @@ -654,6 +654,10 @@ impl<'a> Parser<'a> { return Ok(Type::Reference(Arc::new(typ))); } + if self.eat_keyword(Keyword::Function)? { + return Ok(Type::Function); + } + self.expected_type() } diff --git a/compiler/noirc_evaluator/src/ssa/parser/tests.rs b/compiler/noirc_evaluator/src/ssa/parser/tests.rs index 60d398bf9d5..a9c0f225784 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/tests.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/tests.rs @@ -441,3 +441,15 @@ fn test_negative() { "; assert_ssa_roundtrip(src); } + +#[test] +fn test_function_type() { + let src = " + acir(inline) fn main f0 { + b0(): + v0 = allocate -> &mut function + return + } + "; + assert_ssa_roundtrip(src); +} diff --git a/compiler/noirc_evaluator/src/ssa/parser/token.rs b/compiler/noirc_evaluator/src/ssa/parser/token.rs index f663879e899..54c4950f89c 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/token.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/token.rs @@ -130,6 +130,7 @@ pub(crate) enum Keyword { Field, Fold, Fn, + Function, IncRc, Index, Jmp, @@ -185,6 +186,7 @@ impl Keyword { "Field" => Keyword::Field, "fold" => Keyword::Fold, "fn" => Keyword::Fn, + "function" => Keyword::Function, "inc_rc" => Keyword::IncRc, "index" => Keyword::Index, "jmp" => Keyword::Jmp, @@ -242,6 +244,7 @@ impl Display for Keyword { Keyword::Field => write!(f, "Field"), Keyword::Fold => write!(f, "fold"), Keyword::Fn => write!(f, "fn"), + Keyword::Function => write!(f, "function"), Keyword::IncRc => write!(f, "inc_rc"), Keyword::Index => write!(f, "index"), Keyword::Inline => write!(f, "inline"), From 1d403cc07060344ecf7b4d07916af36fbd7767a7 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 22 Nov 2024 07:13:52 -0300 Subject: [PATCH 2/3] Parse assert message static string --- .../noirc_evaluator/src/ssa/ir/printer.rs | 4 +- .../noirc_evaluator/src/ssa/parser/ast.rs | 6 +++ .../src/ssa/parser/into_ssa.rs | 19 ++++++-- .../noirc_evaluator/src/ssa/parser/lexer.rs | 46 ++++++++++++++++++- .../noirc_evaluator/src/ssa/parser/mod.rs | 29 ++++++++++-- .../noirc_evaluator/src/ssa/parser/tests.rs | 12 +++++ .../noirc_evaluator/src/ssa/parser/token.rs | 2 + 7 files changed, 107 insertions(+), 11 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/compiler/noirc_evaluator/src/ssa/ir/printer.rs index b981b81f365..6bebd21fe61 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -272,13 +272,13 @@ fn display_constrain_error( ) -> Result { match error { ConstrainError::StaticString(assert_message_string) => { - writeln!(f, " '{assert_message_string:?}'") + writeln!(f, ", {assert_message_string:?}") } ConstrainError::Dynamic(_, is_string, values) => { if let Some(constant_string) = try_to_extract_string_from_error_payload(*is_string, values, &function.dfg) { - writeln!(f, " '{}'", constant_string) + writeln!(f, ", {constant_string:?}") } else { writeln!(f, ", data {}", value_list(function, values)) } diff --git a/compiler/noirc_evaluator/src/ssa/parser/ast.rs b/compiler/noirc_evaluator/src/ssa/parser/ast.rs index a34b7fd70d3..0d006a06614 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/ast.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/ast.rs @@ -89,6 +89,7 @@ pub(crate) enum ParsedInstruction { Constrain { lhs: ParsedValue, rhs: ParsedValue, + assert_message: Option, }, DecrementRc { value: ParsedValue, @@ -129,6 +130,11 @@ pub(crate) enum ParsedInstruction { }, } +#[derive(Debug)] +pub(crate) enum AssertMessage { + String(String), +} + #[derive(Debug)] pub(crate) enum ParsedTerminator { Jmp { destination: Identifier, arguments: Vec }, diff --git a/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs b/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs index 552ac0781c7..26f3de05ece 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs @@ -2,12 +2,15 @@ use std::collections::HashMap; use crate::ssa::{ function_builder::FunctionBuilder, - ir::{basic_block::BasicBlockId, function::FunctionId, value::ValueId}, + ir::{ + basic_block::BasicBlockId, function::FunctionId, instruction::ConstrainError, + value::ValueId, + }, }; use super::{ - Identifier, ParsedBlock, ParsedFunction, ParsedInstruction, ParsedSsa, ParsedTerminator, - ParsedValue, RuntimeType, Ssa, SsaError, + ast::AssertMessage, Identifier, ParsedBlock, ParsedFunction, ParsedInstruction, ParsedSsa, + ParsedTerminator, ParsedValue, RuntimeType, Ssa, SsaError, }; impl ParsedSsa { @@ -198,10 +201,16 @@ impl Translator { let value_id = self.builder.insert_cast(lhs, typ); self.define_variable(target, value_id)?; } - ParsedInstruction::Constrain { lhs, rhs } => { + ParsedInstruction::Constrain { lhs, rhs, assert_message } => { let lhs = self.translate_value(lhs)?; let rhs = self.translate_value(rhs)?; - self.builder.insert_constrain(lhs, rhs, None); + let assert_message = match assert_message { + Some(AssertMessage::String(string)) => { + Some(ConstrainError::StaticString(string)) + } + None => None, + }; + self.builder.insert_constrain(lhs, rhs, assert_message); } ParsedInstruction::DecrementRc { value } => { let value = self.translate_value(value)?; diff --git a/compiler/noirc_evaluator/src/ssa/parser/lexer.rs b/compiler/noirc_evaluator/src/ssa/parser/lexer.rs index 4c90475be74..d89bc1e9e28 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/lexer.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/lexer.rs @@ -61,6 +61,7 @@ impl<'a> Lexer<'a> { Some('&') => self.single_char_token(Token::Ampersand), Some('-') if self.peek_char() == Some('>') => self.double_char_token(Token::Arrow), Some('-') => self.single_char_token(Token::Dash), + Some('"') => self.eat_string_literal(), Some(ch) if ch.is_ascii_alphanumeric() || ch == '_' => self.eat_alpha_numeric(ch), Some(char) => Err(LexerError::UnexpectedCharacter { char, @@ -177,6 +178,41 @@ impl<'a> Lexer<'a> { Ok(integer_token.into_span(start, end)) } + fn eat_string_literal(&mut self) -> SpannedTokenResult { + let start = self.position; + let mut string = String::new(); + + while let Some(next) = self.next_char() { + let char = match next { + '"' => break, + '\\' => match self.next_char() { + Some('r') => '\r', + Some('n') => '\n', + Some('t') => '\t', + Some('0') => '\0', + Some('"') => '"', + Some('\\') => '\\', + Some(escaped) => { + let span = Span::inclusive(start, self.position); + return Err(LexerError::InvalidEscape { escaped, span }); + } + None => { + let span = Span::inclusive(start, self.position); + return Err(LexerError::UnterminatedStringLiteral { span }); + } + }, + other => other, + }; + + string.push(char); + } + + let str_literal_token = Token::Str(string); + + let end = self.position; + Ok(str_literal_token.into_span(start, end)) + } + fn eat_while bool>( &mut self, initial_char: Option, @@ -247,6 +283,12 @@ pub(crate) enum LexerError { InvalidIntegerLiteral { span: Span, found: String }, #[error("Integer literal too large")] IntegerLiteralTooLarge { span: Span, limit: String }, + #[error("Unterminated string literal")] + UnterminatedStringLiteral { span: Span }, + #[error( + "'\\{escaped}' is not a valid escape sequence. Use '\\' for a literal backslash character." + )] + InvalidEscape { escaped: char, span: Span }, } impl LexerError { @@ -254,7 +296,9 @@ impl LexerError { match self { LexerError::UnexpectedCharacter { span, .. } | LexerError::InvalidIntegerLiteral { span, .. } - | LexerError::IntegerLiteralTooLarge { span, .. } => *span, + | LexerError::IntegerLiteralTooLarge { span, .. } + | LexerError::UnterminatedStringLiteral { span } + | LexerError::InvalidEscape { span, .. } => *span, } } } diff --git a/compiler/noirc_evaluator/src/ssa/parser/mod.rs b/compiler/noirc_evaluator/src/ssa/parser/mod.rs index b54fe73b8df..411ffd96450 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/mod.rs @@ -10,8 +10,8 @@ use super::{ use acvm::{AcirField, FieldElement}; use ast::{ - Identifier, ParsedBlock, ParsedFunction, ParsedInstruction, ParsedParameter, ParsedSsa, - ParsedValue, + AssertMessage, Identifier, ParsedBlock, ParsedFunction, ParsedInstruction, ParsedParameter, + ParsedSsa, ParsedValue, }; use lexer::{Lexer, LexerError}; use noirc_errors::Span; @@ -313,7 +313,18 @@ impl<'a> Parser<'a> { let lhs = self.parse_value_or_error()?; self.eat_or_error(Token::Equal)?; let rhs = self.parse_value_or_error()?; - Ok(Some(ParsedInstruction::Constrain { lhs, rhs })) + + let assert_message = if self.eat(Token::Comma)? { + if let Some(str) = self.eat_str()? { + Some(AssertMessage::String(str)) + } else { + todo!("Dynamic assert message") + } + } else { + None + }; + + Ok(Some(ParsedInstruction::Constrain { lhs, rhs, assert_message })) } fn parse_decrement_rc(&mut self) -> ParseResult> { @@ -771,6 +782,18 @@ impl<'a> Parser<'a> { } } + fn eat_str(&mut self) -> ParseResult> { + if matches!(self.token.token(), Token::Str(..)) { + let token = self.bump()?; + match token.into_token() { + Token::Str(string) => Ok(Some(string)), + _ => unreachable!(), + } + } else { + Ok(None) + } + } + fn eat(&mut self, token: Token) -> ParseResult { if self.token.token() == &token { self.bump()?; diff --git a/compiler/noirc_evaluator/src/ssa/parser/tests.rs b/compiler/noirc_evaluator/src/ssa/parser/tests.rs index a9c0f225784..918e5e3d44c 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/tests.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/tests.rs @@ -214,6 +214,18 @@ fn test_constrain() { assert_ssa_roundtrip(src); } +#[test] +fn test_constrain_with_static_message() { + let src = r#" + acir(inline) fn main f0 { + b0(v0: Field): + constrain v0 == Field 1, "Oh no!" + return + } + "#; + assert_ssa_roundtrip(src); +} + #[test] fn test_enable_side_effects() { let src = " diff --git a/compiler/noirc_evaluator/src/ssa/parser/token.rs b/compiler/noirc_evaluator/src/ssa/parser/token.rs index 54c4950f89c..a055a08d113 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/token.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/token.rs @@ -29,6 +29,7 @@ impl SpannedToken { pub(crate) enum Token { Ident(String), Int(FieldElement), + Str(String), Keyword(Keyword), IntType(IntType), /// = @@ -77,6 +78,7 @@ impl Display for Token { match self { Token::Ident(ident) => write!(f, "{}", ident), Token::Int(int) => write!(f, "{}", int), + Token::Str(string) => write!(f, "{string:?}"), Token::Keyword(keyword) => write!(f, "{}", keyword), Token::IntType(int_type) => write!(f, "{}", int_type), Token::Assign => write!(f, "="), From 74ca9d12a604cc9f6aa102b92e7ec6f302b57b89 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 22 Nov 2024 08:36:40 -0300 Subject: [PATCH 3/3] Parse dynamic error messages --- .../noirc_evaluator/src/ssa/parser/ast.rs | 3 ++- .../src/ssa/parser/into_ssa.rs | 24 ++++++++++++++++--- .../noirc_evaluator/src/ssa/parser/mod.rs | 16 +++++++++++-- .../noirc_evaluator/src/ssa/parser/tests.rs | 13 ++++++++++ .../noirc_evaluator/src/ssa/parser/token.rs | 3 +++ 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/parser/ast.rs b/compiler/noirc_evaluator/src/ssa/parser/ast.rs index 0d006a06614..6c7608a2f16 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/ast.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/ast.rs @@ -132,7 +132,8 @@ pub(crate) enum ParsedInstruction { #[derive(Debug)] pub(crate) enum AssertMessage { - String(String), + Static(String), + Dynamic(Vec), } #[derive(Debug)] diff --git a/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs b/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs index 26f3de05ece..e78cbbd75a1 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use acvm::acir::circuit::ErrorSelector; + use crate::ssa::{ function_builder::FunctionBuilder, ir::{ @@ -34,6 +36,8 @@ struct Translator { /// passes already which replaced some of the original IDs. The translator /// will recreate the SSA step by step, which can result in a new ID layout. variables: HashMap>, + + error_selector_counter: u64, } impl Translator { @@ -67,8 +71,13 @@ impl Translator { functions.insert(function.internal_name.clone(), function_id); } - let mut translator = - Self { builder, functions, variables: HashMap::new(), blocks: HashMap::new() }; + let mut translator = Self { + builder, + functions, + variables: HashMap::new(), + blocks: HashMap::new(), + error_selector_counter: 0, + }; translator.translate_function_body(main_function)?; Ok(translator) @@ -205,9 +214,18 @@ impl Translator { let lhs = self.translate_value(lhs)?; let rhs = self.translate_value(rhs)?; let assert_message = match assert_message { - Some(AssertMessage::String(string)) => { + Some(AssertMessage::Static(string)) => { Some(ConstrainError::StaticString(string)) } + Some(AssertMessage::Dynamic(values)) => { + let error_selector = ErrorSelector::new(self.error_selector_counter); + self.error_selector_counter += 1; + + let is_string_type = false; + let values = self.translate_values(values)?; + + Some(ConstrainError::Dynamic(error_selector, is_string_type, values)) + } None => None, }; self.builder.insert_constrain(lhs, rhs, assert_message); diff --git a/compiler/noirc_evaluator/src/ssa/parser/mod.rs b/compiler/noirc_evaluator/src/ssa/parser/mod.rs index 411ffd96450..3d8bd37dead 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/mod.rs @@ -316,9 +316,11 @@ impl<'a> Parser<'a> { let assert_message = if self.eat(Token::Comma)? { if let Some(str) = self.eat_str()? { - Some(AssertMessage::String(str)) + Some(AssertMessage::Static(str)) + } else if self.eat_keyword(Keyword::Data)? { + Some(AssertMessage::Dynamic(self.parse_comma_separated_values()?)) } else { - todo!("Dynamic assert message") + return self.expected_string_or_data(); } } else { None @@ -839,6 +841,13 @@ impl<'a> Parser<'a> { }) } + fn expected_string_or_data(&mut self) -> ParseResult { + Err(ParserError::ExpectedStringOrData { + found: self.token.token().clone(), + span: self.token.to_span(), + }) + } + fn expected_identifier(&mut self) -> ParseResult { Err(ParserError::ExpectedIdentifier { found: self.token.token().clone(), @@ -900,6 +909,8 @@ pub(crate) enum ParserError { ExpectedType { found: Token, span: Span }, #[error("Expected an instruction or terminator, found '{found}'")] ExpectedInstructionOrTerminator { found: Token, span: Span }, + #[error("Expected a string literal or 'data', found '{found}'")] + ExpectedStringOrData { found: Token, span: Span }, #[error("Expected a value, found '{found}'")] ExpectedValue { found: Token, span: Span }, #[error("Multiple return values only allowed for call")] @@ -916,6 +927,7 @@ impl ParserError { | ParserError::ExpectedInt { span, .. } | ParserError::ExpectedType { span, .. } | ParserError::ExpectedInstructionOrTerminator { span, .. } + | ParserError::ExpectedStringOrData { span, .. } | ParserError::ExpectedValue { 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 918e5e3d44c..593b66d0c98 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/tests.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/tests.rs @@ -226,6 +226,19 @@ fn test_constrain_with_static_message() { assert_ssa_roundtrip(src); } +#[test] +fn test_constrain_with_dynamic_message() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field, v1: Field): + v7 = make_array [u8 123, u8 120, u8 125, u8 32, u8 123, u8 121, u8 125] : [u8; 7] + constrain v0 == Field 1, data v7, u32 2, v0, v1 + return + } + "; + assert_ssa_roundtrip(src); +} + #[test] fn test_enable_side_effects() { let src = " diff --git a/compiler/noirc_evaluator/src/ssa/parser/token.rs b/compiler/noirc_evaluator/src/ssa/parser/token.rs index a055a08d113..d8dd4ec011e 100644 --- a/compiler/noirc_evaluator/src/ssa/parser/token.rs +++ b/compiler/noirc_evaluator/src/ssa/parser/token.rs @@ -122,6 +122,7 @@ pub(crate) enum Keyword { Call, Cast, Constrain, + Data, DecRc, Div, Inline, @@ -178,6 +179,7 @@ impl Keyword { "call" => Keyword::Call, "cast" => Keyword::Cast, "constrain" => Keyword::Constrain, + "data" => Keyword::Data, "dec_rc" => Keyword::DecRc, "div" => Keyword::Div, "else" => Keyword::Else, @@ -238,6 +240,7 @@ impl Display for Keyword { Keyword::Call => write!(f, "call"), Keyword::Cast => write!(f, "cast"), Keyword::Constrain => write!(f, "constrain"), + Keyword::Data => write!(f, "data"), Keyword::DecRc => write!(f, "dec_rc"), Keyword::Div => write!(f, "div"), Keyword::Else => write!(f, "else"),