From ff8ca91efc925bf8bdbe7f2d2feb651981c5c1b9 Mon Sep 17 00:00:00 2001 From: jfecher Date: Tue, 23 Jul 2024 11:02:11 -0500 Subject: [PATCH] feat: Implement `zeroed` in the interpreter (#5540) # Description ## Problem\* Resolves ## 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. --- .../src/hir/comptime/interpreter.rs | 12 ++- .../src/hir/comptime/interpreter/builtin.rs | 90 ++++++++++++++++++- .../noirc_frontend/src/hir/comptime/value.rs | 5 ++ 3 files changed, 102 insertions(+), 5 deletions(-) diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs index 31ee8ef7200..5e11454bc0b 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -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(); @@ -134,6 +135,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { &mut self, function: FuncId, arguments: Vec<(Value, Location)>, + return_type: Type, location: Location, ) -> IResult { let attributes = self.elaborator.interner.function_attributes(&function); @@ -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 }) diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 771f23335fd..692fe3feae4 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -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}, @@ -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 { match name { @@ -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 }) @@ -408,6 +411,87 @@ fn trait_constraint_eq( Ok(Value::Bool(constraint_a == constraint_b)) } +// fn zeroed() -> T +fn zeroed(return_type: Type, location: Location) -> IResult { + 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)>, diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index a5f35e144cd..f29b67bfc4e 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -51,6 +51,7 @@ pub enum Value { TraitDefinition(TraitId), FunctionDefinition(FuncId), ModuleDefinition(ModuleId), + Zeroed(Type), } impl Value { @@ -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), }) } @@ -215,6 +217,7 @@ impl Value { | Value::TraitConstraint(_) | Value::TraitDefinition(_) | Value::FunctionDefinition(_) + | Value::Zeroed(_) | Value::ModuleDefinition(_) => { return Err(InterpreterError::CannotInlineMacro { value: self, location }) } @@ -329,6 +332,7 @@ impl Value { | Value::TraitConstraint(_) | Value::TraitDefinition(_) | Value::FunctionDefinition(_) + | Value::Zeroed(_) | Value::ModuleDefinition(_) => { return Err(InterpreterError::CannotInlineMacro { value: self, location }) } @@ -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})"), } } }