From 57a1356f3131a4283057f2b367e4f0c765249e7a Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Tue, 21 Mar 2023 16:38:53 +0000 Subject: [PATCH 1/3] Add zeroed builtin --- .../src/ssa/acir_gen/operations/intrinsics.rs | 8 ++++++++ crates/noirc_evaluator/src/ssa/builtin.rs | 6 +++++- noir_stdlib/src/lib.nr | 1 + noir_stdlib/src/unsafe.nr | 5 +++++ 4 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 noir_stdlib/src/unsafe.nr diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs index 9982c04e900..13a0159e8fb 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs @@ -124,6 +124,14 @@ pub(crate) fn evaluate( unreachable!(); } } + Opcode::Zeroed => { + let bit_size = ctx.zero(); + let l_c = var_cache.get_or_compute_internal_var_unwrap(args[0], evaluator, ctx); + outputs = to_radix_base(l_c.expression(), 2, bit_size, endianess, evaluator); + if let ObjectType::Pointer(a) = res_type { + memory_map.map_array(a, &outputs, ctx); + } + } } // If more than witness is returned, diff --git a/crates/noirc_evaluator/src/ssa/builtin.rs b/crates/noirc_evaluator/src/ssa/builtin.rs index 2ca9effe5df..159d81603d8 100644 --- a/crates/noirc_evaluator/src/ssa/builtin.rs +++ b/crates/noirc_evaluator/src/ssa/builtin.rs @@ -17,6 +17,7 @@ pub(crate) enum Opcode { ToRadix(Endian), Println(PrintlnInfo), Sort, + Zeroed, } impl std::fmt::Display for Opcode { @@ -41,6 +42,7 @@ impl Opcode { Some(Opcode::Println(PrintlnInfo { is_string_output: false, show_output: true })) } "arraysort" => Some(Opcode::Sort), + "zeroed" => Some(Opcode::Zeroed), _ => BlackBoxFunc::lookup(op_name).map(Opcode::LowLevel), } } @@ -64,6 +66,7 @@ impl Opcode { } Opcode::Println(_) => "println", Opcode::Sort => "arraysort", + Opcode::Zeroed => "zeroed", } } @@ -92,7 +95,7 @@ impl Opcode { } } } - Opcode::ToBits(_) | Opcode::ToRadix(_) | Opcode::Println(_) | Opcode::Sort => { + Opcode::ToBits(_) | Opcode::ToRadix(_) | Opcode::Println(_) | Opcode::Sort | Opcode::Zeroed => { BigUint::zero() } //pointers do not overflow } @@ -128,6 +131,7 @@ impl Opcode { let a = super::mem::Memory::deref(ctx, args[0]).unwrap(); (ctx.mem[a].len, ctx.mem[a].element_type) } + Opcode::Zeroed => (1, ctx.object_type(args[0])), } } } diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index abdd56c4975..16383c2c704 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -8,6 +8,7 @@ mod sha256; mod sha512; mod field; mod ec; +mod unsafe; #[builtin(println)] fn println(_input : T) {} diff --git a/noir_stdlib/src/unsafe.nr b/noir_stdlib/src/unsafe.nr new file mode 100644 index 00000000000..a28549d5011 --- /dev/null +++ b/noir_stdlib/src/unsafe.nr @@ -0,0 +1,5 @@ +/// For any type, return an instance of that type by initializing +/// all of its fields to 0. This is considered to be unsafe since there +/// is no guarantee that all zeroes is a valid bit pattern for every type. +#[builtin(zeroed)] +fn zeroed() -> T {} From b3e6765f62b640b210909741ce2b76984148dae1 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Tue, 28 Mar 2023 11:28:24 +0100 Subject: [PATCH 2/3] Implement std::unsafe::zeroed --- .../src/ssa/acir_gen/operations/intrinsics.rs | 8 -- crates/noirc_evaluator/src/ssa/builtin.rs | 6 +- .../src/monomorphization/mod.rs | 127 +++++++++++++----- 3 files changed, 94 insertions(+), 47 deletions(-) diff --git a/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs b/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs index fb2b0e2378a..9ba2322d730 100644 --- a/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs +++ b/crates/noirc_evaluator/src/ssa/acir_gen/operations/intrinsics.rs @@ -131,14 +131,6 @@ pub(crate) fn evaluate( unreachable!(); } } - Opcode::Zeroed => { - let bit_size = ctx.zero(); - let l_c = var_cache.get_or_compute_internal_var_unwrap(args[0], evaluator, ctx); - outputs = to_radix_base(l_c.expression(), 2, bit_size, endianess, evaluator); - if let ObjectType::Pointer(a) = res_type { - memory_map.map_array(a, &outputs, ctx); - } - } } // If more than witness is returned, diff --git a/crates/noirc_evaluator/src/ssa/builtin.rs b/crates/noirc_evaluator/src/ssa/builtin.rs index 159d81603d8..2ca9effe5df 100644 --- a/crates/noirc_evaluator/src/ssa/builtin.rs +++ b/crates/noirc_evaluator/src/ssa/builtin.rs @@ -17,7 +17,6 @@ pub(crate) enum Opcode { ToRadix(Endian), Println(PrintlnInfo), Sort, - Zeroed, } impl std::fmt::Display for Opcode { @@ -42,7 +41,6 @@ impl Opcode { Some(Opcode::Println(PrintlnInfo { is_string_output: false, show_output: true })) } "arraysort" => Some(Opcode::Sort), - "zeroed" => Some(Opcode::Zeroed), _ => BlackBoxFunc::lookup(op_name).map(Opcode::LowLevel), } } @@ -66,7 +64,6 @@ impl Opcode { } Opcode::Println(_) => "println", Opcode::Sort => "arraysort", - Opcode::Zeroed => "zeroed", } } @@ -95,7 +92,7 @@ impl Opcode { } } } - Opcode::ToBits(_) | Opcode::ToRadix(_) | Opcode::Println(_) | Opcode::Sort | Opcode::Zeroed => { + Opcode::ToBits(_) | Opcode::ToRadix(_) | Opcode::Println(_) | Opcode::Sort => { BigUint::zero() } //pointers do not overflow } @@ -131,7 +128,6 @@ impl Opcode { let a = super::mem::Memory::deref(ctx, args[0]).unwrap(); (ctx.mem[a].len, ctx.mem[a].element_type) } - Opcode::Zeroed => (1, ctx.object_type(args[0])), } } } diff --git a/crates/noirc_frontend/src/monomorphization/mod.rs b/crates/noirc_frontend/src/monomorphization/mod.rs index d852db9ac24..286653623f1 100644 --- a/crates/noirc_frontend/src/monomorphization/mod.rs +++ b/crates/noirc_frontend/src/monomorphization/mod.rs @@ -593,12 +593,8 @@ impl<'interner> Monomorphizer<'interner> { let return_type = Self::convert_type(&return_type); let location = call.location; - self.try_evaluate_call(&func, &call.arguments).unwrap_or(ast::Expression::Call(ast::Call { - func, - arguments, - return_type, - location, - })) + self.try_evaluate_call(&func, &call.arguments, &return_type) + .unwrap_or(ast::Expression::Call(ast::Call { func, arguments, return_type, location })) } /// Try to evaluate certain builtin functions (currently only 'array_len' and field modulus methods) @@ -608,50 +604,47 @@ impl<'interner> Monomorphizer<'interner> { /// To fix this we need to evaluate on the identifier instead, which /// requires us to evaluate to a Lambda value which isn't in noir yet. fn try_evaluate_call( - &self, + &mut self, func: &ast::Expression, arguments: &[node_interner::ExprId], + result_type: &ast::Type, ) -> Option { - match func { - ast::Expression::Ident(ident) => match &ident.definition { - Definition::Builtin(opcode) if opcode == "array_len" => { + if let ast::Expression::Ident(ident) = func { + if let Definition::Builtin(opcode) = &ident.definition { + if opcode == "array_len" { let typ = self.interner.id_type(arguments[0]); let len = typ.evaluate_to_u64().unwrap(); - Some(ast::Expression::Literal(ast::Literal::Integer( + return Some(ast::Expression::Literal(ast::Literal::Integer( (len as u128).into(), ast::Type::Field, - ))) - } - Definition::Builtin(opcode) if opcode == "modulus_num_bits" => { - Some(ast::Expression::Literal(ast::Literal::Integer( + ))); + } else if opcode == "modulus_num_bits" { + return Some(ast::Expression::Literal(ast::Literal::Integer( (FieldElement::max_num_bits() as u128).into(), ast::Type::Field, - ))) + ))); + } else if opcode == "zeroed" { + return Some(self.zeroed_value_of_type(result_type)); } - Definition::Builtin(opcode) if opcode == "modulus_le_bits" => { - let modulus = FieldElement::modulus(); + + let modulus = FieldElement::modulus(); + + if opcode == "modulus_le_bits" { let bits = modulus.to_radix_le(2); - Some(self.modulus_array_literal(bits, 1)) - } - Definition::Builtin(opcode) if opcode == "modulus_be_bits" => { - let modulus = FieldElement::modulus(); + return Some(self.modulus_array_literal(bits, 1)); + } else if opcode == "modulus_be_bits" { let bits = modulus.to_radix_be(2); - Some(self.modulus_array_literal(bits, 1)) - } - Definition::Builtin(opcode) if opcode == "modulus_be_bytes" => { - let modulus = FieldElement::modulus(); + return Some(self.modulus_array_literal(bits, 1)); + } else if opcode == "modulus_be_bytes" { let bytes = modulus.to_bytes_be(); - Some(self.modulus_array_literal(bytes, 8)) - } - Definition::Builtin(opcode) if opcode == "modulus_le_bytes" => { - let modulus = FieldElement::modulus(); + return Some(self.modulus_array_literal(bytes, 8)); + } else if opcode == "modulus_le_bytes" { let bytes = modulus.to_bytes_le(); - Some(self.modulus_array_literal(bytes, 8)) + return Some(self.modulus_array_literal(bytes, 8)); } - _ => None, - }, - _ => None, + } } + None } fn modulus_array_literal(&self, bytes: Vec, arr_elem_bits: u32) -> ast::Expression { @@ -754,6 +747,72 @@ impl<'interner> Monomorphizer<'interner> { typ, }) } + + /// Implements std::unsafe::zeroed by returning an appropriate zeroed + /// ast literal or collection node for the given type. Note that for functions + /// there is no obvious zeroed value so this should be considered unsafe to use. + fn zeroed_value_of_type(&mut self, typ: &ast::Type) -> ast::Expression { + match typ { + ast::Type::Field | ast::Type::Integer(..) => { + ast::Expression::Literal(ast::Literal::Integer(0_u128.into(), typ.clone())) + } + ast::Type::Bool => ast::Expression::Literal(ast::Literal::Bool(false)), + // There is no unit literal currently. Replace it with 'false' since it should be ignored + // anyway. + ast::Type::Unit => ast::Expression::Literal(ast::Literal::Bool(false)), + ast::Type::Array(length, element_type) => { + let element = self.zeroed_value_of_type(element_type.as_ref()); + ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { + contents: vec![element; *length as usize], + element_type: element_type.as_ref().clone(), + })) + } + ast::Type::String(length) => { + ast::Expression::Literal(ast::Literal::Str("\0".repeat(*length as usize))) + } + ast::Type::Tuple(fields) => { + ast::Expression::Tuple(vecmap(fields, |field| self.zeroed_value_of_type(field))) + } + ast::Type::Function(parameter_types, ret_type) => { + self.create_zeroed_function(parameter_types, ret_type) + } + } + } + + // Creating a zeroed function value is almost always an error if it is used later, + // Hence why std::unsafe::zeroed is unsafe. + // + // To avoid confusing later passes, we arbitrarily choose to construct a function + // that satisfies the input type by discarding all its parameters and returning a + // zeroed value of the result type. + fn create_zeroed_function( + &mut self, + parameter_types: &[ast::Type], + ret_type: &ast::Type, + ) -> ast::Expression { + let lambda_name = "zeroed_lambda"; + + let parameters = vecmap(parameter_types, |parameter_type| { + (self.next_local_id(), false, "_".into(), parameter_type.clone()) + }); + + let body = self.zeroed_value_of_type(ret_type); + + let id = self.next_function_id(); + let return_type = ret_type.clone(); + let name = lambda_name.to_owned(); + + let function = ast::Function { id, name, parameters, body, return_type }; + self.push_function(id, function); + + ast::Expression::Ident(ast::Ident { + definition: Definition::Function(id), + mutable: false, + location: None, + name: lambda_name.to_owned(), + typ: ast::Type::Function(parameter_types.to_owned(), Box::new(ret_type.clone())), + }) + } } fn unwrap_tuple_type(typ: &HirType) -> Vec { From a8d0692f5c9199d6da3dca4519087e40b6866e39 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Thu, 30 Mar 2023 14:09:14 +0100 Subject: [PATCH 3/3] Fix merge conflict --- crates/noirc_frontend/src/monomorphization/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/noirc_frontend/src/monomorphization/mod.rs b/crates/noirc_frontend/src/monomorphization/mod.rs index a129a771d1d..354db5f7508 100644 --- a/crates/noirc_frontend/src/monomorphization/mod.rs +++ b/crates/noirc_frontend/src/monomorphization/mod.rs @@ -807,7 +807,8 @@ impl<'interner> Monomorphizer<'interner> { let return_type = ret_type.clone(); let name = lambda_name.to_owned(); - let function = ast::Function { id, name, parameters, body, return_type }; + let unconstrained = false; + let function = ast::Function { id, name, parameters, body, return_type, unconstrained }; self.push_function(id, function); ast::Expression::Ident(ast::Ident {