Skip to content

Commit

Permalink
feat: Implement zeroed in the interpreter (#5540)
Browse files Browse the repository at this point in the history
# Description

## Problem\*

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

## Summary\*

Implements `zeroed` in the interpreter

## Additional Context

This is still a draft because I wanted to call attention to the
inability to actually implement `zeroed` when the types may still
sometimes not be known while interpretering. In this case we still don't
want to fail since these zeroed values aren't meant to be used anyway -
we'd rather return any dummy value. Here I've done it by adding
`Value::Zeroed` but I think we should perhaps consider changing `zeroed`
itself to return a dummy value of any type rather than one that is
guaranteed to be some zero equivalent.

## Documentation\*

Check one:
- [x] No documentation needed.
- [ ] Documentation included in this PR.
- [ ] **[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 Jul 23, 2024
1 parent 9e61e97 commit ff8ca91
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 5 deletions.
12 changes: 10 additions & 2 deletions compiler/noirc_frontend/src/hir/comptime/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
}

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

let parameters = meta.parameters.0.clone();
Expand All @@ -134,6 +135,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
&mut self,
function: FuncId,
arguments: Vec<(Value, Location)>,
return_type: Type,
location: Location,
) -> IResult<Value> {
let attributes = self.elaborator.interner.function_attributes(&function);
Expand All @@ -142,7 +144,13 @@ 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, location)
builtin::call_builtin(
self.elaborator.interner,
&builtin,
arguments,
return_type,
location,
)
} else if let Some(foreign) = func_attrs.foreign() {
let item = format!("Comptime evaluation for foreign functions like {foreign}");
Err(InterpreterError::Unimplemented { item, location })
Expand Down
90 changes: 87 additions & 3 deletions compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ use std::{

use acvm::{AcirField, FieldElement};
use chumsky::Parser;
use iter_extended::vecmap;
use noirc_errors::Location;
use iter_extended::{try_vecmap, vecmap};
use noirc_errors::{Location, Span};
use rustc_hash::FxHashMap as HashMap;

use crate::{
ast::{IntegerBitSize, TraitBound},
Expand All @@ -15,13 +16,14 @@ use crate::{
node_interner::TraitId,
parser,
token::{SpannedToken, Token, Tokens},
QuotedType, Type,
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 {
Expand All @@ -48,6 +50,7 @@ pub(super) fn call_builtin(
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 })
Expand Down Expand Up @@ -408,6 +411,87 @@ fn trait_constraint_eq(
Ok(Value::Bool(constraint_a == constraint_b))
}

// fn zeroed<T>() -> T
fn zeroed(return_type: Type, location: Location) -> IResult<Value> {
match return_type {
Type::FieldElement => Ok(Value::Field(0u128.into())),
Type::Array(length_type, elem) => {
if let Some(length) = length_type.evaluate_to_u32() {
let element = zeroed(elem.as_ref().clone(), location)?;
let array = std::iter::repeat(element).take(length as usize).collect();
Ok(Value::Array(array, Type::Array(length_type, elem)))
} else {
// Assume we can resolve the length later
Ok(Value::Zeroed(Type::Array(length_type, elem)))
}
}
Type::Slice(_) => Ok(Value::Slice(im::Vector::new(), return_type)),
Type::Integer(sign, bits) => match (sign, bits) {
(Signedness::Unsigned, IntegerBitSize::One) => Ok(Value::U8(0)),
(Signedness::Unsigned, IntegerBitSize::Eight) => Ok(Value::U8(0)),
(Signedness::Unsigned, IntegerBitSize::Sixteen) => Ok(Value::U16(0)),
(Signedness::Unsigned, IntegerBitSize::ThirtyTwo) => Ok(Value::U32(0)),
(Signedness::Unsigned, IntegerBitSize::SixtyFour) => Ok(Value::U64(0)),
(Signedness::Signed, IntegerBitSize::One) => Ok(Value::I8(0)),
(Signedness::Signed, IntegerBitSize::Eight) => Ok(Value::I8(0)),
(Signedness::Signed, IntegerBitSize::Sixteen) => Ok(Value::I16(0)),
(Signedness::Signed, IntegerBitSize::ThirtyTwo) => Ok(Value::I32(0)),
(Signedness::Signed, IntegerBitSize::SixtyFour) => Ok(Value::I64(0)),
},
Type::Bool => Ok(Value::Bool(false)),
Type::String(length_type) => {
if let Some(length) = length_type.evaluate_to_u32() {
Ok(Value::String(Rc::new("\0".repeat(length as usize))))
} else {
// Assume we can resolve the length later
Ok(Value::Zeroed(Type::String(length_type)))
}
}
Type::FmtString(_, _) => {
let item = "format strings in a comptime context".into();
Err(InterpreterError::Unimplemented { item, location })
}
Type::Unit => Ok(Value::Unit),
Type::Tuple(fields) => {
Ok(Value::Tuple(try_vecmap(fields, |field| zeroed(field, location))?))
}
Type::Struct(struct_type, generics) => {
let fields = struct_type.borrow().get_fields(&generics);
let mut values = HashMap::default();

for (field_name, field_type) in fields {
let field_value = zeroed(field_type, location)?;
values.insert(Rc::new(field_name), field_value);
}

let typ = Type::Struct(struct_type, generics);
Ok(Value::Struct(values, typ))
}
Type::Alias(alias, generics) => zeroed(alias.borrow().get_type(&generics), location),
typ @ Type::Function(..) => {
// Using Value::Zeroed here is probably safer than using FuncId::dummy_id() or similar
Ok(Value::Zeroed(typ))
}
Type::MutableReference(element) => {
let element = zeroed(*element, location)?;
Ok(Value::Pointer(Shared::new(element), false))
}
Type::Quoted(QuotedType::TraitConstraint) => Ok(Value::TraitConstraint(TraitBound {
trait_path: Path::from_single(String::new(), Span::default()),
trait_id: None,
trait_generics: Vec::new(),
})),
// Optimistically assume we can resolve this type later or that the value is unused
Type::TypeVariable(_, _)
| Type::Forall(_, _)
| Type::Constant(_)
| Type::Quoted(_)
| Type::Error
| Type::TraitAsType(_, _, _)
| Type::NamedGeneric(_, _, _) => Ok(Value::Zeroed(return_type)),
}
}

fn modulus_be_bits(
_interner: &mut NodeInterner,
arguments: Vec<(Value, Location)>,
Expand Down
5 changes: 5 additions & 0 deletions compiler/noirc_frontend/src/hir/comptime/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub enum Value {
TraitDefinition(TraitId),
FunctionDefinition(FuncId),
ModuleDefinition(ModuleId),
Zeroed(Type),
}

impl Value {
Expand Down Expand Up @@ -94,6 +95,7 @@ impl Value {
Value::TraitDefinition(_) => Type::Quoted(QuotedType::TraitDefinition),
Value::FunctionDefinition(_) => Type::Quoted(QuotedType::FunctionDefinition),
Value::ModuleDefinition(_) => Type::Quoted(QuotedType::Module),
Value::Zeroed(typ) => return Cow::Borrowed(typ),
})
}

Expand Down Expand Up @@ -215,6 +217,7 @@ impl Value {
| Value::TraitConstraint(_)
| Value::TraitDefinition(_)
| Value::FunctionDefinition(_)
| Value::Zeroed(_)
| Value::ModuleDefinition(_) => {
return Err(InterpreterError::CannotInlineMacro { value: self, location })
}
Expand Down Expand Up @@ -329,6 +332,7 @@ impl Value {
| Value::TraitConstraint(_)
| Value::TraitDefinition(_)
| Value::FunctionDefinition(_)
| Value::Zeroed(_)
| Value::ModuleDefinition(_) => {
return Err(InterpreterError::CannotInlineMacro { value: self, location })
}
Expand Down Expand Up @@ -438,6 +442,7 @@ impl Display for Value {
Value::TraitDefinition(_) => write!(f, "(trait definition)"),
Value::FunctionDefinition(_) => write!(f, "(function definition)"),
Value::ModuleDefinition(_) => write!(f, "(module)"),
Value::Zeroed(typ) => write!(f, "(zeroed {typ})"),
}
}
}

0 comments on commit ff8ca91

Please sign in to comment.