From 49ab121ef21819e028d407999a689b92c67d8df7 Mon Sep 17 00:00:00 2001 From: guipublic <47281315+guipublic@users.noreply.github.com> Date: Wed, 20 Sep 2023 15:38:35 +0200 Subject: [PATCH] feat: add wrapping functions in stdlib and use them in relevant test cases (#2725) --- .../ssa/acir_gen/acir_ir/generated_acir.rs | 4 +++ compiler/noirc_evaluator/src/ssa/ir/dfg.rs | 2 +- .../noirc_evaluator/src/ssa/ir/instruction.rs | 21 +++++++++++++--- .../src/ssa/ir/instruction/call.rs | 12 +++++++++ noir_stdlib/src/lib.nr | 25 +++++++++++++++++++ .../tests/execution_success/4_sub/src/main.nr | 3 ++- .../execution_success/5_over/src/main.nr | 4 ++- .../execution_success/6_array/src/main.nr | 7 +++--- .../signed_division/src/main.nr | 7 +++--- 9 files changed, 73 insertions(+), 12 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 33f55e76de4..0adb14246b7 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -452,6 +452,10 @@ impl GeneratedAcir { if let Some(rhs_const) = rhs.to_const() { max_rhs_bits = rhs_const.num_bits(); if max_rhs_bits != 0 { + if max_rhs_bits > max_bit_size { + let zero = self.get_or_create_witness(&Expression::zero()); + return Ok((zero, zero)); + } max_q_bits = max_bit_size - max_rhs_bits + 1; } } diff --git a/compiler/noirc_evaluator/src/ssa/ir/dfg.rs b/compiler/noirc_evaluator/src/ssa/ir/dfg.rs index 73914c54674..242278bd581 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/dfg.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/dfg.rs @@ -161,7 +161,7 @@ impl DataFlowGraph { call_stack: CallStack, ) -> InsertInstructionResult { use InsertInstructionResult::*; - match instruction.simplify(self, block) { + match instruction.simplify(self, block, ctrl_typevars.clone()) { SimplifyResult::SimplifiedTo(simplification) => SimplifiedTo(simplification), SimplifyResult::SimplifiedToMultiple(simplification) => { SimplifiedToMultiple(simplification) diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 1dd2368b1a0..c911c3f1ed7 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -44,6 +44,8 @@ pub(crate) enum Intrinsic { ToBits(Endian), ToRadix(Endian), BlackBox(BlackBoxFunc), + FromField, + AsField, } impl std::fmt::Display for Intrinsic { @@ -64,6 +66,8 @@ impl std::fmt::Display for Intrinsic { Intrinsic::ToRadix(Endian::Big) => write!(f, "to_be_radix"), Intrinsic::ToRadix(Endian::Little) => write!(f, "to_le_radix"), Intrinsic::BlackBox(function) => write!(f, "{function}"), + Intrinsic::FromField => write!(f, "from_field"), + Intrinsic::AsField => write!(f, "as_field"), } } } @@ -86,7 +90,9 @@ impl Intrinsic { | Intrinsic::SliceRemove | Intrinsic::StrAsBytes | Intrinsic::ToBits(_) - | Intrinsic::ToRadix(_) => false, + | Intrinsic::ToRadix(_) + | Intrinsic::FromField + | Intrinsic::AsField => false, // Some black box functions have side-effects Intrinsic::BlackBox(func) => matches!(func, BlackBoxFunc::RecursiveAggregation), @@ -111,6 +117,8 @@ impl Intrinsic { "to_be_radix" => Some(Intrinsic::ToRadix(Endian::Big)), "to_le_bits" => Some(Intrinsic::ToBits(Endian::Little)), "to_be_bits" => Some(Intrinsic::ToBits(Endian::Big)), + "from_field" => Some(Intrinsic::FromField), + "as_field" => Some(Intrinsic::AsField), other => BlackBoxFunc::lookup(other).map(Intrinsic::BlackBox), } } @@ -357,7 +365,12 @@ impl Instruction { /// /// The `block` parameter indicates the block this new instruction will be inserted into /// after this call. - pub(crate) fn simplify(&self, dfg: &mut DataFlowGraph, block: BasicBlockId) -> SimplifyResult { + pub(crate) fn simplify( + &self, + dfg: &mut DataFlowGraph, + block: BasicBlockId, + ctrl_typevars: Option>, + ) -> SimplifyResult { use SimplifyResult::*; match self { Instruction::Binary(binary) => binary.simplify(dfg), @@ -425,7 +438,9 @@ impl Instruction { None } } - Instruction::Call { func, arguments } => simplify_call(*func, arguments, dfg, block), + Instruction::Call { func, arguments } => { + simplify_call(*func, arguments, dfg, block, ctrl_typevars) + } Instruction::EnableSideEffects { condition } => { if let Some(last) = dfg[block].instructions().last().copied() { let last = &mut dfg[last]; diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 42d0aa0a4e4..52a39c9e40c 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -27,6 +27,7 @@ pub(super) fn simplify_call( arguments: &[ValueId], dfg: &mut DataFlowGraph, block: BasicBlockId, + ctrl_typevars: Option>, ) -> SimplifyResult { let intrinsic = match &dfg[func] { Value::Intrinsic(intrinsic) => *intrinsic, @@ -223,6 +224,17 @@ pub(super) fn simplify_call( } Intrinsic::BlackBox(bb_func) => simplify_black_box_func(bb_func, arguments, dfg), Intrinsic::Sort => simplify_sort(dfg, arguments), + Intrinsic::AsField => { + let instruction = Instruction::Cast( + arguments[0], + Type::Numeric(crate::ssa::ir::types::NumericType::NativeField), + ); + SimplifyResult::SimplifiedToInstruction(instruction) + } + Intrinsic::FromField => { + let instruction = Instruction::Cast(arguments[0], ctrl_typevars.unwrap().remove(0)); + SimplifyResult::SimplifiedToInstruction(instruction) + } } } diff --git a/noir_stdlib/src/lib.nr b/noir_stdlib/src/lib.nr index 224c3a03f21..faade2a30aa 100644 --- a/noir_stdlib/src/lib.nr +++ b/noir_stdlib/src/lib.nr @@ -35,3 +35,28 @@ fn verify_proof(_verification_key : [Field], _proof : [Field], _public_inputs // Useful for debugging for-loop bounds. #[builtin(assert_constant)] fn assert_constant(_x: T) {} + +#[builtin(from_field)] +fn from_field(x : Field) -> T {} + +#[builtin(as_field)] +fn as_field(x : T) -> Field {} + + +fn wrapping_add(x : T, y: T) -> T { + crate::from_field(crate::as_field(x) + crate::as_field(y)) +} + + +fn wrapping_sub(x : T, y: T) -> T { + //340282366920938463463374607431768211456 is 2^128, it is used to avoid underflow + crate::from_field(crate::as_field(x) + 340282366920938463463374607431768211456 - crate::as_field(y)) +} + +fn wrapping_mul(x : T, y: T) -> T { + crate::from_field(crate::as_field(x) * crate::as_field(y)) +} + +fn wrapping_shift_left(x : T, y: T) -> T { + crate::from_field(crate::as_field(x) * 2.pow_32(crate::as_field(y))) +} \ No newline at end of file diff --git a/tooling/nargo_cli/tests/execution_success/4_sub/src/main.nr b/tooling/nargo_cli/tests/execution_success/4_sub/src/main.nr index 43ea9d5b4f0..60bcde9c0b3 100644 --- a/tooling/nargo_cli/tests/execution_success/4_sub/src/main.nr +++ b/tooling/nargo_cli/tests/execution_success/4_sub/src/main.nr @@ -1,6 +1,7 @@ +use dep::std; // Test unsafe integer subtraction with underflow: 12 - 2418266113 = 1876701195 modulo 2^32 fn main(mut x: u32, y: u32, z: u32) { - x -= y; + x = std::wrapping_sub(x,y); assert(x == z); // Test constant underflow (regression for #2045) diff --git a/tooling/nargo_cli/tests/execution_success/5_over/src/main.nr b/tooling/nargo_cli/tests/execution_success/5_over/src/main.nr index 4fdff16c5c0..fa50fbe7c14 100644 --- a/tooling/nargo_cli/tests/execution_success/5_over/src/main.nr +++ b/tooling/nargo_cli/tests/execution_success/5_over/src/main.nr @@ -1,7 +1,9 @@ +use dep::std; + // Test unsafe integer arithmetic // Test odd bits integer fn main(mut x: u32, y: u32) { - x = x * x; + x = std::wrapping_mul(x,x); assert(y == x); let c:u3 = 2; diff --git a/tooling/nargo_cli/tests/execution_success/6_array/src/main.nr b/tooling/nargo_cli/tests/execution_success/6_array/src/main.nr index cfdcf34d3ad..8d029943d81 100644 --- a/tooling/nargo_cli/tests/execution_success/6_array/src/main.nr +++ b/tooling/nargo_cli/tests/execution_success/6_array/src/main.nr @@ -1,3 +1,4 @@ +use dep::std; //Basic tests for arrays fn main(x: [u32; 5], y: [u32; 5], mut z: u32, t: u32) { let mut c = 2301; @@ -13,8 +14,8 @@ fn main(x: [u32; 5], y: [u32; 5], mut z: u32, t: u32) { c = 2301 as u32; for i in 0..5 { c = t+2 as u32; - c = z*z*x[i]; - z += x[i]*y[i] - c; + c = std::wrapping_mul(std::wrapping_mul(z,z),x[i]); + z =std::wrapping_add(z, std::wrapping_sub(x[i]*y[i] , c)); } assert(z == 3814912846); @@ -25,7 +26,7 @@ fn main(x: [u32; 5], y: [u32; 5], mut z: u32, t: u32) { z = z + x[i]*y[i]; for _i in 0..3 { c = i as u32 - 2 as u32; - z *= c; + z = std::wrapping_mul(z,c); } } assert(z == 41472); diff --git a/tooling/nargo_cli/tests/execution_success/signed_division/src/main.nr b/tooling/nargo_cli/tests/execution_success/signed_division/src/main.nr index 7bc6d7fc936..651df10e963 100644 --- a/tooling/nargo_cli/tests/execution_success/signed_division/src/main.nr +++ b/tooling/nargo_cli/tests/execution_success/signed_division/src/main.nr @@ -1,3 +1,4 @@ +use dep::std; // Testing signed integer division: // 7/3 = 2 // -7/3 = -2 @@ -8,9 +9,9 @@ fn main(mut x: i32, mut y: i32, mut z: i32) { assert(x / y == z); // -7/3 = -2 - let minus_x = 0-x; - let minus_z = 0-z; - let minus_y = 0-y; + let minus_x = std::wrapping_sub(0,x); + let minus_z = std::wrapping_sub(0,z); + let minus_y = std::wrapping_sub(0,y); assert(x+minus_x == 0); assert(z+minus_z == 0); assert(minus_x / y == minus_z);