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

feat: Implement println in the comptime interpreter #5197

Merged
merged 5 commits into from
Jun 7, 2024
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/noirc_frontend/src/ast/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub enum ExpressionKind {
Lambda(Box<Lambda>),
Parenthesized(Box<Expression>),
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
Expand Down Expand Up @@ -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"),
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/elaborator/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/hir/comptime/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 61 additions & 7 deletions compiler/noirc_frontend/src/hir/comptime/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,6 @@
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(),
Expand All @@ -72,6 +67,10 @@
});
}

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)?;
Expand All @@ -84,6 +83,35 @@
Ok(result)
}

fn call_builtin(
&mut self,
function: FuncId,
arguments: Vec<(Value, Location)>,
location: Location,
) -> IResult<Value> {
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}'")

Check warning on line 111 in compiler/noirc_frontend/src/hir/comptime/interpreter.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lowlevel)
}
}

fn call_closure(
&mut self,
closure: HirLambda,
Expand Down Expand Up @@ -219,7 +247,8 @@
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)?;
michaeljklein marked this conversation as resolved.
Show resolved Hide resolved
self.current_scope_mut().insert(id, argument);
Ok(())
}
Expand Down Expand Up @@ -360,7 +389,11 @@
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),
}
Expand Down Expand Up @@ -454,6 +487,14 @@
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 })
}
Expand Down Expand Up @@ -1269,4 +1310,17 @@
pub(super) fn evaluate_comptime(&mut self, statement: StmtId) -> IResult<Value> {
self.evaluate_statement(statement)
}

fn print_oracle(&self, arguments: Vec<(Value, Location)>) -> Result<Value, InterpreterError> {
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)
}
}
52 changes: 49 additions & 3 deletions compiler/noirc_frontend/src/hir/comptime/value.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -306,3 +306,49 @@ impl Value {
fn unwrap_rc<T: Clone>(rc: Rc<T>) -> 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!(),
}
}
}
4 changes: 3 additions & 1 deletion compiler/noirc_frontend/src/hir/resolution/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
),
Expand Down
11 changes: 9 additions & 2 deletions compiler/noirc_frontend/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,20 +702,27 @@ pub enum FunctionAttribute {
}

impl FunctionAttribute {
pub fn builtin(self) -> Option<String> {
pub fn builtin(&self) -> Option<&String> {
match self {
FunctionAttribute::Builtin(name) => Some(name),
_ => None,
}
}

pub fn foreign(self) -> Option<String> {
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(_))
}
Expand Down
23 changes: 9 additions & 14 deletions compiler/noirc_frontend/src/monomorphization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -216,34 +215,30 @@ 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 =
self.queue_function(id, expr_id, typ, turbofish_generics, trait_method);
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");
Expand Down
4 changes: 3 additions & 1 deletion compiler/noirc_frontend/src/parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

use chumsky::prelude::*;
use iter_extended::vecmap;
use lalrpop_util::lalrpop_mod;

Check warning on line 50 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lalrpop)

Check warning on line 50 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lalrpop)
use noirc_errors::{Span, Spanned};

mod assertion;
Expand All @@ -60,8 +60,8 @@
mod structs;
mod traits;

// synthesized by LALRPOP

Check warning on line 63 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (LALRPOP)
lalrpop_mod!(pub noir_parser);

Check warning on line 64 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lalrpop)

#[cfg(test)]
mod test_helpers;
Expand All @@ -85,12 +85,12 @@

if cfg!(feature = "experimental_parser") {
for parsed_item in &parsed_module.items {
if lalrpop_parser_supports_kind(&parsed_item.kind) {

Check warning on line 88 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lalrpop)
match &parsed_item.kind {
ItemKind::Import(parsed_use_tree) => {
prototype_parse_use_tree(Some(parsed_use_tree), source_program);
}
// other kinds prevented by lalrpop_parser_supports_kind

Check warning on line 93 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (lalrpop)
_ => unreachable!(),
}
}
Expand All @@ -107,7 +107,7 @@
}

let mut lexer = Lexer::new(input);
lexer = lexer.skip_whitespaces(false);

Check warning on line 110 in compiler/noirc_frontend/src/parser/parser.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (whitespaces)
let mut errors = Vec::new();

// NOTE: this is a hack to get the references working
Expand Down Expand Up @@ -545,7 +545,9 @@
where
S: NoirParser<StatementKind> + '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<StatementKind> + 'a
Expand Down
7 changes: 7 additions & 0 deletions test_programs/execution_success/comptime_println/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "comptime_println"
type = "bin"
authors = [""]
compiler_version = ">=0.27.0"

[dependencies]
7 changes: 7 additions & 0 deletions test_programs/execution_success/comptime_println/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn main() {
let x = comptime {
println("hello from compile-time!");
1 + 2
};
println(x);
}
4 changes: 2 additions & 2 deletions tooling/nargo_fmt/src/rewrite/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(_) => {
Expand Down
Loading