Skip to content

Commit

Permalink
feat: Implement Value::Type in comptime interpreter (#5593)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

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

## Summary\*

Implements `Value::Type` now that we have a way to call the elaborator
within the interpreter.
- Changed a few existing methods to return `Type`s instead of `Quoted`
- Added `Quoted::as_type`

## 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.

---------

Co-authored-by: Michael J Klein <michaeljklein@users.noreply.github.com>
  • Loading branch information
jfecher and michaeljklein authored Jul 24, 2024
1 parent a23fac3 commit 4c3bf97
Show file tree
Hide file tree
Showing 16 changed files with 220 additions and 100 deletions.
19 changes: 9 additions & 10 deletions compiler/noirc_frontend/src/elaborator/comptime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ use std::mem::replace;

use crate::{
hir_def::expr::HirIdent,
macros_api::Expression,
node_interner::{DependencyId, ExprId, FuncId},
node_interner::{DependencyId, FuncId},
};

use super::{Elaborator, FunctionContext, ResolverMeta};
Expand All @@ -12,22 +11,22 @@ impl<'context> Elaborator<'context> {
/// Elaborate an expression from the middle of a comptime scope.
/// When this happens we require additional information to know
/// what variables should be in scope.
pub fn elaborate_expression_from_comptime(
pub fn elaborate_item_from_comptime<T>(
&mut self,
expr: Expression,
function: Option<FuncId>,
) -> ExprId {
current_function: Option<FuncId>,
f: impl FnOnce(&mut Self) -> T,
) -> T {
self.function_context.push(FunctionContext::default());
let old_scope = self.scopes.end_function();
self.scopes.start_function();
let function_id = function.map(DependencyId::Function);
let function_id = current_function.map(DependencyId::Function);
let old_item = replace(&mut self.current_item, function_id);

// Note: recover_generics isn't good enough here because any existing generics
// should not be in scope of this new function
let old_generics = std::mem::take(&mut self.generics);

let old_crate_and_module = function.map(|function| {
let old_crate_and_module = current_function.map(|function| {
let meta = self.interner.function_meta(&function);
let old_crate = replace(&mut self.crate_id, meta.source_crate);
let old_module = replace(&mut self.local_module, meta.source_module);
Expand All @@ -36,7 +35,7 @@ impl<'context> Elaborator<'context> {
});

self.populate_scope_from_comptime_scopes();
let expr = self.elaborate_expression(expr).0;
let result = f(self);

if let Some((old_crate, old_module)) = old_crate_and_module {
self.crate_id = old_crate;
Expand All @@ -48,7 +47,7 @@ impl<'context> Elaborator<'context> {
self.scopes.end_function();
self.scopes.0.push(old_scope);
self.check_and_pop_function_context();
expr
result
}

fn populate_scope_from_comptime_scopes(&mut self) {
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 @@ -35,7 +35,7 @@ use crate::{
use super::{Elaborator, LambdaContext};

impl<'context> Elaborator<'context> {
pub(super) fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) {
pub(crate) fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) {
let (hir_expr, typ) = match expr.kind {
ExpressionKind::Literal(literal) => self.elaborate_literal(literal, expr.span),
ExpressionKind::Block(block) => self.elaborate_block(block),
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/elaborator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub const WILDCARD_TYPE: &str = "_";

impl<'context> Elaborator<'context> {
/// Translates an UnresolvedType to a Type with a `TypeKind::Normal`
pub(super) fn resolve_type(&mut self, typ: UnresolvedType) -> Type {
pub(crate) fn resolve_type(&mut self, typ: UnresolvedType) -> Type {
let span = typ.span;
let resolved_type = self.resolve_type_inner(typ, &Kind::Normal);
if resolved_type.is_nested_slice() {
Expand Down
34 changes: 22 additions & 12 deletions compiler/noirc_frontend/src/hir/comptime/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
perform_instantiation_bindings(&instantiation_bindings);
let impl_bindings =
perform_impl_bindings(self.elaborator.interner, trait_method, function, location)?;
let old_function = self.current_function.replace(function);

let result = self.call_function_inner(function, arguments, location);

self.current_function = old_function;
undo_instantiation_bindings(impl_bindings);
undo_instantiation_bindings(instantiation_bindings);
result
Expand Down Expand Up @@ -110,9 +108,25 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {

if meta.kind != FunctionKind::Normal {
let return_type = meta.return_type().follow_bindings();
return self.call_builtin(function, arguments, return_type, location);
return self.call_special(function, arguments, return_type, location);
}

// Wait until after call_special to set the current function so that builtin functions like
// `.as_type()` still call the resolver in the caller's scope.
let old_function = self.current_function.replace(function);
let result = self.call_user_defined_function(function, arguments, location);
self.current_function = old_function;
result
}

/// Call a non-builtin function
fn call_user_defined_function(
&mut self,
function: FuncId,
arguments: Vec<(Value, Location)>,
location: Location,
) -> IResult<Value> {
let meta = self.elaborator.interner.function_meta(&function);
let parameters = meta.parameters.0.clone();
let previous_state = self.enter_function();

Expand All @@ -132,7 +146,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
Ok(result)
}

fn call_builtin(
fn call_special(
&mut self,
function: FuncId,
arguments: Vec<(Value, Location)>,
Expand All @@ -145,13 +159,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {

if let Some(builtin) = func_attrs.builtin() {
let builtin = builtin.clone();
builtin::call_builtin(
self.elaborator.interner,
&builtin,
arguments,
return_type,
location,
)
self.call_builtin(&builtin, arguments, return_type, location)
} else if let Some(foreign) = func_attrs.foreign() {
let foreign = foreign.clone();
foreign::call_foreign(self.elaborator.interner, &foreign, arguments, location)
Expand Down Expand Up @@ -1124,7 +1132,9 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
let expr = result.into_expression(self.elaborator.interner, location)?;
let expr = self
.elaborator
.elaborate_expression_from_comptime(expr, self.current_function);
.elaborate_item_from_comptime(self.current_function, |elab| {
elab.elaborate_expression(expr).0
});
result = self.evaluate(expr)?;
}
Ok(result)
Expand Down
137 changes: 81 additions & 56 deletions compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,49 @@ use crate::{
QuotedType, Shared, Type,
};

pub(super) fn call_builtin(
interner: &mut NodeInterner,
name: &str,
arguments: Vec<(Value, Location)>,
return_type: Type,
location: Location,
) -> IResult<Value> {
match name {
"array_len" => array_len(interner, arguments, location),
"as_slice" => as_slice(interner, arguments, location),
"is_unconstrained" => Ok(Value::Bool(true)),
"modulus_be_bits" => modulus_be_bits(interner, arguments, location),
"modulus_be_bytes" => modulus_be_bytes(interner, arguments, location),
"modulus_le_bits" => modulus_le_bits(interner, arguments, location),
"modulus_le_bytes" => modulus_le_bytes(interner, arguments, location),
"modulus_num_bits" => modulus_num_bits(interner, arguments, location),
"slice_insert" => slice_insert(interner, arguments, location),
"slice_pop_back" => slice_pop_back(interner, arguments, location),
"slice_pop_front" => slice_pop_front(interner, arguments, location),
"slice_push_back" => slice_push_back(interner, arguments, location),
"slice_push_front" => slice_push_front(interner, arguments, location),
"slice_remove" => slice_remove(interner, arguments, location),
"struct_def_as_type" => struct_def_as_type(interner, arguments, location),
"struct_def_fields" => struct_def_fields(interner, arguments, location),
"struct_def_generics" => struct_def_generics(interner, arguments, location),
"trait_constraint_eq" => trait_constraint_eq(interner, arguments, location),
"trait_constraint_hash" => trait_constraint_hash(interner, arguments, location),
"trait_def_as_trait_constraint" => {
trait_def_as_trait_constraint(interner, arguments, location)
}
"quoted_as_trait_constraint" => quoted_as_trait_constraint(interner, arguments, location),
"zeroed" => zeroed(return_type, location),
_ => {
let item = format!("Comptime evaluation for builtin function {name}");
Err(InterpreterError::Unimplemented { item, location })
use super::Interpreter;

impl<'local, 'context> Interpreter<'local, 'context> {
pub(super) fn call_builtin(
&mut self,
name: &str,
arguments: Vec<(Value, Location)>,
return_type: Type,
location: Location,
) -> IResult<Value> {
let interner = &mut self.elaborator.interner;
match name {
"array_len" => array_len(interner, arguments, location),
"as_slice" => as_slice(interner, arguments, location),
"is_unconstrained" => Ok(Value::Bool(true)),
"modulus_be_bits" => modulus_be_bits(interner, arguments, location),
"modulus_be_bytes" => modulus_be_bytes(interner, arguments, location),
"modulus_le_bits" => modulus_le_bits(interner, arguments, location),
"modulus_le_bytes" => modulus_le_bytes(interner, arguments, location),
"modulus_num_bits" => modulus_num_bits(interner, arguments, location),
"slice_insert" => slice_insert(interner, arguments, location),
"slice_pop_back" => slice_pop_back(interner, arguments, location),
"slice_pop_front" => slice_pop_front(interner, arguments, location),
"slice_push_back" => slice_push_back(interner, arguments, location),
"slice_push_front" => slice_push_front(interner, arguments, location),
"slice_remove" => slice_remove(interner, arguments, location),
"struct_def_as_type" => struct_def_as_type(interner, arguments, location),
"struct_def_fields" => struct_def_fields(interner, arguments, location),
"struct_def_generics" => struct_def_generics(interner, arguments, location),
"trait_constraint_eq" => trait_constraint_eq(interner, arguments, location),
"trait_constraint_hash" => trait_constraint_hash(interner, arguments, location),
"trait_def_as_trait_constraint" => {
trait_def_as_trait_constraint(interner, arguments, location)
}
"quoted_as_trait_constraint" => {
quoted_as_trait_constraint(interner, arguments, location)
}
"quoted_as_type" => quoted_as_type(self, arguments, location),
"zeroed" => zeroed(return_type, location),
_ => {
let item = format!("Comptime evaluation for builtin function {name}");
Err(InterpreterError::Unimplemented { item, location })
}
}
}
}
Expand Down Expand Up @@ -203,36 +211,31 @@ fn slice_push_back(
Ok(Value::Slice(values, typ))
}

/// fn as_type(self) -> Quoted
/// fn as_type(self) -> Type
fn struct_def_as_type(
interner: &NodeInterner,
mut arguments: Vec<(Value, Location)>,
location: Location,
) -> IResult<Value> {
check_argument_count(1, &arguments, location)?;

let (struct_def, span) = match arguments.pop().unwrap() {
(Value::StructDefinition(id), location) => (id, location.span),
let struct_def = match arguments.pop().unwrap().0 {
Value::StructDefinition(id) => id,
value => {
let expected = Type::Quoted(QuotedType::StructDefinition);
return Err(InterpreterError::TypeMismatch { expected, location, value: value.0 });
return Err(InterpreterError::TypeMismatch { expected, location, value });
}
};

let struct_def = interner.get_struct(struct_def);
let struct_def = struct_def.borrow();
let make_token = |name| SpannedToken::new(Token::Ident(name), span);

let mut tokens = vec![make_token(struct_def.name.to_string())];
let struct_def_rc = interner.get_struct(struct_def);
let struct_def = struct_def_rc.borrow();

for (i, generic) in struct_def.generics.iter().enumerate() {
if i != 0 {
tokens.push(SpannedToken::new(Token::Comma, span));
}
tokens.push(make_token(generic.type_var.borrow().to_string()));
}
let generics = vecmap(&struct_def.generics, |generic| {
Type::NamedGeneric(generic.type_var.clone(), generic.name.clone(), generic.kind.clone())
});

Ok(Value::Code(Rc::new(Tokens(tokens))))
drop(struct_def);
Ok(Value::Type(Type::Struct(struct_def_rc, generics)))
}

/// fn generics(self) -> [Quoted]
Expand Down Expand Up @@ -263,7 +266,7 @@ fn struct_def_generics(
Ok(Value::Slice(generics.collect(), typ))
}

/// fn fields(self) -> [(Quoted, Quoted)]
/// fn fields(self) -> [(Quoted, Type)]
/// Returns (name, type) pairs of each field of this StructDefinition
fn struct_def_fields(
interner: &mut NodeInterner,
Expand All @@ -290,15 +293,13 @@ fn struct_def_fields(

for (name, typ) in struct_def.get_fields_as_written() {
let name = make_quoted(vec![make_token(name)]);
let id = interner.push_quoted_type(typ);
let typ = SpannedToken::new(Token::QuotedType(id), span);
let typ = Value::Code(Rc::new(Tokens(vec![typ])));
let typ = Value::Type(typ);
fields.push_back(Value::Tuple(vec![name, typ]));
}

let typ = Type::Slice(Box::new(Type::Tuple(vec![
Type::Quoted(QuotedType::Quoted),
Type::Quoted(QuotedType::Quoted),
Type::Quoted(QuotedType::Type),
])));
Ok(Value::Slice(fields, typ))
}
Expand Down Expand Up @@ -404,6 +405,30 @@ fn quoted_as_trait_constraint(
Ok(Value::TraitConstraint(trait_bound))
}

// fn as_type(quoted: Quoted) -> Type
fn quoted_as_type(
interpreter: &mut Interpreter,
mut arguments: Vec<(Value, Location)>,
location: Location,
) -> IResult<Value> {
check_argument_count(1, &arguments, location)?;

let tokens = get_quoted(arguments.pop().unwrap().0, location)?;
let quoted = tokens.as_ref().clone();

let typ = parser::parse_type().parse(quoted).map_err(|mut errors| {
let error = errors.swap_remove(0);
let rule = "a type";
InterpreterError::FailedToParseMacro { error, tokens, rule, file: location.file }
})?;

let typ = interpreter
.elaborator
.elaborate_item_from_comptime(interpreter.current_function, |elab| elab.resolve_type(typ));

Ok(Value::Type(typ))
}

// fn constraint_hash(constraint: TraitConstraint) -> Field
fn trait_constraint_hash(
_interner: &mut NodeInterner,
Expand Down
19 changes: 5 additions & 14 deletions compiler/noirc_frontend/src/hir/comptime/interpreter/unquote.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use noirc_errors::Location;

use crate::{
hir::comptime::{errors::IResult, value::unwrap_rc, Value},
token::{SpannedToken, Token, Tokens},
hir::comptime::errors::IResult,
token::{Token, Tokens},
};

use super::Interpreter;
Expand All @@ -19,20 +19,11 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
let mut new_tokens = Vec::with_capacity(tokens.0.len());

for token in tokens.0 {
let span = token.to_span();
match token.token() {
Token::UnquoteMarker(id) => {
match self.evaluate(*id)? {
// If the value is already quoted we don't want to change the token stream by
// turning it into a Quoted block (which would add `quote`, `{`, and `}` tokens).
Value::Code(stream) => new_tokens.extend(unwrap_rc(stream).0),
value => {
let new_id =
value.into_hir_expression(self.elaborator.interner, location)?;
let new_token = Token::UnquoteMarker(new_id);
new_tokens.push(SpannedToken::new(new_token, span));
}
}
let value = self.evaluate(*id)?;
let tokens = value.into_tokens(self.elaborator.interner, location)?;
new_tokens.extend(tokens.0);
}
_ => new_tokens.push(token),
}
Expand Down
Loading

0 comments on commit 4c3bf97

Please sign in to comment.