From ee05f51497c217d7d1729e8b20249159c99c727c Mon Sep 17 00:00:00 2001 From: jfecher Date: Fri, 7 Jun 2024 15:17:12 +0100 Subject: [PATCH] feat: Implement println in the comptime interpreter (#5197) # Description ## Problem\* Resolves ## Summary\* ## Additional Context ## Documentation\* Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. - [x] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- compiler/noirc_frontend/src/ast/expression.rs | 4 +- .../src/elaborator/expressions.rs | 2 +- .../noirc_frontend/src/hir/comptime/errors.rs | 2 +- .../src/hir/comptime/interpreter.rs | 68 +++++++++++++++++-- .../noirc_frontend/src/hir/comptime/value.rs | 52 +++++++++++++- .../src/hir/resolution/resolver.rs | 4 +- compiler/noirc_frontend/src/lexer/token.rs | 11 ++- .../src/monomorphization/mod.rs | 23 +++---- compiler/noirc_frontend/src/parser/parser.rs | 4 +- .../comptime_println/Nargo.toml | 7 ++ .../comptime_println/src/main.nr | 7 ++ tooling/nargo_fmt/src/rewrite/expr.rs | 4 +- 12 files changed, 154 insertions(+), 34 deletions(-) create mode 100644 test_programs/execution_success/comptime_println/Nargo.toml create mode 100644 test_programs/execution_success/comptime_println/src/main.nr diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index 749e41d9c1c..50836add8de 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -34,7 +34,7 @@ pub enum ExpressionKind { Lambda(Box), Parenthesized(Box), Quote(BlockExpression), - Comptime(BlockExpression), + Comptime(BlockExpression, Span), // This variant is only emitted when inlining the result of comptime // code. It is used to translate function values back into the AST while @@ -536,7 +536,7 @@ impl Display for ExpressionKind { Lambda(lambda) => lambda.fmt(f), Parenthesized(sub_expr) => write!(f, "({sub_expr})"), Quote(block) => write!(f, "quote {block}"), - Comptime(block) => write!(f, "comptime {block}"), + Comptime(block, _) => write!(f, "comptime {block}"), Error => write!(f, "Error"), Resolved(_) => write!(f, "?Resolved"), } diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 5eee7ee6c7c..a922f552c4b 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -59,7 +59,7 @@ impl<'context> Elaborator<'context> { ExpressionKind::Lambda(lambda) => self.elaborate_lambda(*lambda), ExpressionKind::Parenthesized(expr) => return self.elaborate_expression(*expr), ExpressionKind::Quote(quote) => self.elaborate_quote(quote), - ExpressionKind::Comptime(comptime) => { + ExpressionKind::Comptime(comptime, _) => { return self.elaborate_comptime_block(comptime, expr.span) } ExpressionKind::Resolved(id) => return (id, self.interner.id_type(id)), diff --git a/compiler/noirc_frontend/src/hir/comptime/errors.rs b/compiler/noirc_frontend/src/hir/comptime/errors.rs index df4bc941f66..05962420f8a 100644 --- a/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -36,7 +36,7 @@ pub enum InterpreterError { CannotInlineMacro { value: Value, location: Location }, UnquoteFoundDuringEvaluation { location: Location }, - Unimplemented { item: &'static str, location: Location }, + Unimplemented { item: String, location: Location }, // Perhaps this should be unreachable! due to type checking also preventing this error? // Currently it and the Continue variant are the only interpreter errors without a Location field diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index e1e19ad653c..dd436144969 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -59,11 +59,6 @@ impl<'a> Interpreter<'a> { let previous_state = self.enter_function(); let meta = self.interner.function_meta(&function); - if meta.kind != FunctionKind::Normal { - let item = "Evaluation for builtin functions"; - return Err(InterpreterError::Unimplemented { item, location }); - } - if meta.parameters.len() != arguments.len() { return Err(InterpreterError::ArgumentCountMismatch { expected: meta.parameters.len(), @@ -72,6 +67,10 @@ impl<'a> Interpreter<'a> { }); } + if meta.kind != FunctionKind::Normal { + return self.call_builtin(function, arguments, location); + } + let parameters = meta.parameters.0.clone(); for ((parameter, typ, _), (argument, arg_location)) in parameters.iter().zip(arguments) { self.define_pattern(parameter, typ, argument, arg_location)?; @@ -84,6 +83,35 @@ impl<'a> Interpreter<'a> { Ok(result) } + fn call_builtin( + &mut self, + function: FuncId, + arguments: Vec<(Value, Location)>, + location: Location, + ) -> IResult { + let attributes = self.interner.function_attributes(&function); + let func_attrs = attributes.function.as_ref() + .expect("all builtin functions must contain a function attribute which contains the opcode which it links to"); + + if let Some(builtin) = func_attrs.builtin() { + let item = format!("Evaluation for builtin functions like {builtin}"); + Err(InterpreterError::Unimplemented { item, location }) + } else if let Some(foreign) = func_attrs.foreign() { + let item = format!("Evaluation for foreign functions like {foreign}"); + Err(InterpreterError::Unimplemented { item, location }) + } else if let Some(oracle) = func_attrs.oracle() { + if oracle == "print" { + self.print_oracle(arguments) + } else { + let item = format!("Evaluation for oracle functions like {oracle}"); + Err(InterpreterError::Unimplemented { item, location }) + } + } else { + let name = self.interner.function_name(&function); + unreachable!("Non-builtin, lowlevel or oracle builtin fn '{name}'") + } + } + fn call_closure( &mut self, closure: HirLambda, @@ -219,7 +247,8 @@ impl<'a> Interpreter<'a> { argument: Value, location: Location, ) -> IResult<()> { - self.type_check(typ, &argument, location)?; + // Temporarily disabled since this fails on generic types + // self.type_check(typ, &argument, location)?; self.current_scope_mut().insert(id, argument); Ok(()) } @@ -360,7 +389,11 @@ impl<'a> Interpreter<'a> { self.evaluate_integer(value, is_negative, id) } HirLiteral::Str(string) => Ok(Value::String(Rc::new(string))), - HirLiteral::FmtStr(_, _) => todo!("Evaluate format strings"), + HirLiteral::FmtStr(_, _) => { + let item = "format strings in a comptime context".into(); + let location = self.interner.expr_location(&id); + Err(InterpreterError::Unimplemented { item, location }) + } HirLiteral::Array(array) => self.evaluate_array(array, id), HirLiteral::Slice(array) => self.evaluate_slice(array, id), } @@ -454,6 +487,14 @@ impl<'a> Interpreter<'a> { Ok(Value::I64(value)) } } + } else if let Type::TypeVariable(variable, TypeVariableKind::IntegerOrField) = &typ { + Ok(Value::Field(value)) + } else if let Type::TypeVariable(variable, TypeVariableKind::Integer) = &typ { + let value: u64 = value + .try_to_u64() + .ok_or(InterpreterError::IntegerOutOfRangeForType { value, typ, location })?; + let value = if is_negative { 0u64.wrapping_sub(value) } else { value }; + Ok(Value::U64(value)) } else { Err(InterpreterError::NonIntegerIntegerLiteral { typ, location }) } @@ -1269,4 +1310,17 @@ impl<'a> Interpreter<'a> { pub(super) fn evaluate_comptime(&mut self, statement: StmtId) -> IResult { self.evaluate_statement(statement) } + + fn print_oracle(&self, arguments: Vec<(Value, Location)>) -> Result { + assert_eq!(arguments.len(), 2); + + let print_newline = arguments[0].0 == Value::Bool(true); + if print_newline { + println!("{}", arguments[1].0); + } else { + print!("{}", arguments[1].0); + } + + Ok(Value::Unit) + } } diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index 43d461ad043..11bbbc7484d 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, rc::Rc}; +use std::{borrow::Cow, fmt::Display, rc::Rc}; use acvm::{AcirField, FieldElement}; use im::Vector; @@ -135,7 +135,7 @@ impl Value { } Value::Closure(_lambda, _env, _typ) => { // TODO: How should a closure's environment be inlined? - let item = "Returning closures from a comptime fn"; + let item = "Returning closures from a comptime fn".into(); return Err(InterpreterError::Unimplemented { item, location }); } Value::Tuple(fields) => { @@ -235,7 +235,7 @@ impl Value { } Value::Closure(_lambda, _env, _typ) => { // TODO: How should a closure's environment be inlined? - let item = "Returning closures from a comptime fn"; + let item = "Returning closures from a comptime fn".into(); return Err(InterpreterError::Unimplemented { item, location }); } Value::Tuple(fields) => { @@ -306,3 +306,49 @@ impl Value { fn unwrap_rc(rc: Rc) -> T { Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()) } + +impl Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Value::Unit => write!(f, "()"), + Value::Bool(value) => { + let msg = if *value { "true" } else { "false" }; + write!(f, "{msg}") + } + Value::Field(value) => write!(f, "{value}"), + Value::I8(value) => write!(f, "{value}"), + Value::I16(value) => write!(f, "{value}"), + Value::I32(value) => write!(f, "{value}"), + Value::I64(value) => write!(f, "{value}"), + Value::U8(value) => write!(f, "{value}"), + Value::U16(value) => write!(f, "{value}"), + Value::U32(value) => write!(f, "{value}"), + Value::U64(value) => write!(f, "{value}"), + Value::String(value) => write!(f, "{value}"), + Value::Function(_, _) => write!(f, "(function)"), + Value::Closure(_, _, _) => write!(f, "(closure)"), + Value::Tuple(fields) => { + let fields = vecmap(fields, ToString::to_string); + write!(f, "({})", fields.join(", ")) + } + Value::Struct(fields, typ) => { + let typename = match typ.follow_bindings() { + Type::Struct(def, _) => def.borrow().name.to_string(), + other => other.to_string(), + }; + let fields = vecmap(fields, |(name, value)| format!("{}: {}", name, value)); + write!(f, "{typename} {{ {} }}", fields.join(", ")) + } + Value::Pointer(value) => write!(f, "&mut {}", value.borrow()), + Value::Array(values, _) => { + let values = vecmap(values, ToString::to_string); + write!(f, "[{}]", values.join(", ")) + } + Value::Slice(values, _) => { + let values = vecmap(values, ToString::to_string); + write!(f, "&[{}]", values.join(", ")) + } + Value::Code(_) => todo!(), + } + } +} diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 4988628db83..01f58ba4c27 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -1642,7 +1642,9 @@ impl<'a> Resolver<'a> { // The quoted expression isn't resolved since we don't want errors if variables aren't defined ExpressionKind::Quote(block) => HirExpression::Quote(block), - ExpressionKind::Comptime(block) => HirExpression::Comptime(self.resolve_block(block)), + ExpressionKind::Comptime(block, _) => { + HirExpression::Comptime(self.resolve_block(block)) + } ExpressionKind::Resolved(_) => unreachable!( "ExpressionKind::Resolved should only be emitted by the comptime interpreter" ), diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index fdda271e79c..950697c2536 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -702,20 +702,27 @@ pub enum FunctionAttribute { } impl FunctionAttribute { - pub fn builtin(self) -> Option { + pub fn builtin(&self) -> Option<&String> { match self { FunctionAttribute::Builtin(name) => Some(name), _ => None, } } - pub fn foreign(self) -> Option { + pub fn foreign(&self) -> Option<&String> { match self { FunctionAttribute::Foreign(name) => Some(name), _ => None, } } + pub fn oracle(&self) -> Option<&String> { + match self { + FunctionAttribute::Oracle(name) => Some(name), + _ => None, + } + } + pub fn is_foreign(&self) -> bool { matches!(self, FunctionAttribute::Foreign(_)) } diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index a25d6488c83..7ecea5c9eac 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -18,7 +18,6 @@ use crate::{ types, }, node_interner::{self, DefinitionKind, NodeInterner, StmtId, TraitImplKind, TraitMethodId}, - token::FunctionAttribute, Type, TypeBinding, TypeBindings, TypeVariable, TypeVariableKind, }; use acvm::{acir::AcirField, FieldElement}; @@ -216,18 +215,18 @@ impl<'interner> Monomorphizer<'interner> { let attributes = self.interner.function_attributes(&id); match self.interner.function_meta(&id).kind { FunctionKind::LowLevel => { - let attribute = attributes.function.clone().expect("all low level functions must contain a function attribute which contains the opcode which it links to"); + let attribute = attributes.function.as_ref().expect("all low level functions must contain a function attribute which contains the opcode which it links to"); let opcode = attribute.foreign().expect( "ice: function marked as foreign, but attribute kind does not match this", ); - Definition::LowLevel(opcode) + Definition::LowLevel(opcode.to_string()) } FunctionKind::Builtin => { - let attribute = attributes.function.clone().expect("all low level functions must contain a function attribute which contains the opcode which it links to"); + let attribute = attributes.function.as_ref().expect("all builtin functions must contain a function attribute which contains the opcode which it links to"); let opcode = attribute.builtin().expect( "ice: function marked as builtin, but attribute kind does not match this", ); - Definition::Builtin(opcode) + Definition::Builtin(opcode.to_string()) } FunctionKind::Normal => { let id = @@ -235,15 +234,11 @@ impl<'interner> Monomorphizer<'interner> { Definition::Function(id) } FunctionKind::Oracle => { - let attr = attributes - .function - .clone() - .expect("Oracle function must have an oracle attribute"); - - match attr { - FunctionAttribute::Oracle(name) => Definition::Oracle(name), - _ => unreachable!("Oracle function must have an oracle attribute"), - } + let attribute = attributes.function.as_ref().expect("all oracle functions must contain a function attribute which contains the opcode which it links to"); + let opcode = attribute.oracle().expect( + "ice: function marked as builtin, but attribute kind does not match this", + ); + Definition::Oracle(opcode.to_string()) } FunctionKind::Recursive => { unreachable!("Only main can be specified as recursive, which should already be checked"); diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 702ea79af9d..cabc788e07d 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -545,7 +545,9 @@ fn comptime_expr<'a, S>(statement: S) -> impl NoirParser + 'a where S: NoirParser + 'a, { - keyword(Keyword::Comptime).ignore_then(block(statement)).map(ExpressionKind::Comptime) + keyword(Keyword::Comptime) + .ignore_then(spanned(block(statement))) + .map(|(block, span)| ExpressionKind::Comptime(block, span)) } fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a diff --git a/test_programs/execution_success/comptime_println/Nargo.toml b/test_programs/execution_success/comptime_println/Nargo.toml new file mode 100644 index 00000000000..7f8ae3a9cb9 --- /dev/null +++ b/test_programs/execution_success/comptime_println/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_println" +type = "bin" +authors = [""] +compiler_version = ">=0.27.0" + +[dependencies] diff --git a/test_programs/execution_success/comptime_println/src/main.nr b/test_programs/execution_success/comptime_println/src/main.nr new file mode 100644 index 00000000000..f9770066c04 --- /dev/null +++ b/test_programs/execution_success/comptime_println/src/main.nr @@ -0,0 +1,7 @@ +fn main() { + let x = comptime { + println("hello from compile-time!"); + 1 + 2 + }; + println(x); +} diff --git a/tooling/nargo_fmt/src/rewrite/expr.rs b/tooling/nargo_fmt/src/rewrite/expr.rs index 9a704717ade..7ff943aea62 100644 --- a/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/tooling/nargo_fmt/src/rewrite/expr.rs @@ -167,8 +167,8 @@ pub(crate) fn rewrite( } ExpressionKind::Lambda(_) => visitor.slice(span).to_string(), ExpressionKind::Quote(block) => format!("quote {}", rewrite_block(visitor, block, span)), - ExpressionKind::Comptime(block) => { - format!("comptime {}", rewrite_block(visitor, block, span)) + ExpressionKind::Comptime(block, block_span) => { + format!("comptime {}", rewrite_block(visitor, block, block_span)) } ExpressionKind::Error => unreachable!(), ExpressionKind::Resolved(_) => {