Skip to content

Commit

Permalink
feat: Implement println in the comptime interpreter (#5197)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

Resolves <!-- Link to GitHub Issue -->

## 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.
  • Loading branch information
jfecher authored Jun 7, 2024
1 parent 42e0eb9 commit 7f08343
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 34 deletions.
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 @@ 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(),
Expand All @@ -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)?;
Expand All @@ -84,6 +83,35 @@ impl<'a> Interpreter<'a> {
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}'")
}
}

fn call_closure(
&mut self,
closure: HirLambda,
Expand Down Expand Up @@ -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(())
}
Expand Down Expand Up @@ -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),
}
Expand Down Expand Up @@ -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 })
}
Expand Down Expand Up @@ -1269,4 +1310,17 @@ impl<'a> Interpreter<'a> {
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 @@ -545,7 +545,9 @@ fn comptime_expr<'a, S>(statement: S) -> impl NoirParser<ExpressionKind> + 'a
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

0 comments on commit 7f08343

Please sign in to comment.