Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d8dc2ab
wrapping mul support for u128
guipublic Apr 7, 2025
e16f163
use trait for wrapping mul based on type
guipublic Apr 7, 2025
86a4295
code review
guipublic Apr 7, 2025
fcaafb5
Merge branch 'master' into gd/issue_7528
guipublic Apr 7, 2025
f74b8a6
Code review
guipublic Apr 7, 2025
8addddb
code review: move trait to ops
guipublic Apr 7, 2025
6643c08
Merge branch 'master' into gd/issue_7528
guipublic Apr 8, 2025
abd5d12
Wrapping operations as trait
guipublic Apr 8, 2025
b80a192
Use a trait per wrapping operation
guipublic Apr 8, 2025
d63110b
update test
guipublic Apr 8, 2025
0bac232
update documentation
guipublic Apr 10, 2025
aee2da1
Merge branch 'master' into gd/issue_7528
guipublic Apr 16, 2025
9543d20
chore: group trait impls by operations
TomAFrench Apr 16, 2025
494a178
chore: replace impl for bool with one for `u1`
TomAFrench Apr 16, 2025
6860954
chore: optimize 128 bit wrapping arithmetic
TomAFrench Apr 16, 2025
c2ac60c
chore: no abusing the type system!
TomAFrench Apr 16, 2025
3a8e9ae
.
TomAFrench Apr 16, 2025
1f628c0
.
TomAFrench Apr 16, 2025
99bca46
Update docs/docs/noir/concepts/data_types/integers.md
TomAFrench Apr 17, 2025
6c709e8
Update docs/docs/noir/concepts/data_types/integers.md
TomAFrench Apr 17, 2025
0831fd3
Merge branch 'master' into gd/issue_7528
guipublic Apr 18, 2025
9dae865
format
guipublic Apr 18, 2025
bac28ce
Merge branch 'master' into gd/issue_7528
guipublic Apr 18, 2025
105de45
typo
guipublic Apr 18, 2025
a439b7d
Merge branch 'master' into gd/issue_7528
guipublic Apr 18, 2025
123a950
update snapshots
guipublic Apr 18, 2025
7754f01
snapshots..
guipublic Apr 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions docs/docs/noir/concepts/data_types/integers.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,19 @@ fn main() {

### Wrapping methods

Although integer overflow is expected to error, some use-cases rely on wrapping. For these use-cases, the standard library provides `wrapping` variants of certain common operations:
Although integer overflow is expected to error, some use-cases rely on wrapping. For these use-cases, the standard library provides `wrapping` variants of certain common operations via Wrapping traits in `std::ops`

```rust
fn wrapping_add<T>(x: T, y: T) -> T;
fn wrapping_sub<T>(x: T, y: T) -> T;
fn wrapping_mul<T>(x: T, y: T) -> T;
fn wrapping_add(self, y: Self) -> Self;
fn wrapping_sub(self, y: Self) -> Self;
fn wrapping_mul(self, y: Self) -> Self;
```

Example of how it is used:

```rust

use std::ops::WrappingAdd
fn main(x: u8, y: u8) -> pub u8 {
std::wrapping_add(x, y)
x.wrapping_add(y)
}
```
29 changes: 14 additions & 15 deletions noir_stdlib/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,15 @@ pub fn assert_constant<T>(x: T) {}
#[builtin(static_assert)]
pub fn static_assert<let N: u32>(predicate: bool, message: str<N>) {}

#[deprecated("wrapping operations should be done with the Wrapping traits. E.g: x.wrapping_add(y)")]
pub fn wrapping_add<T>(x: T, y: T) -> T
where
T: AsPrimitive<Field>,
Field: AsPrimitive<T>,
{
AsPrimitive::as_(x.as_() + y.as_())
}

#[deprecated("wrapping operations should be done with the Wrapping traits. E.g: x.wrapping_sub(y)")]
pub fn wrapping_sub<T>(x: T, y: T) -> T
where
T: AsPrimitive<Field>,
Expand All @@ -104,7 +105,7 @@ where
//340282366920938463463374607431768211456 is 2^128, it is used to avoid underflow
AsPrimitive::as_(x.as_() + 340282366920938463463374607431768211456 - y.as_())
}

#[deprecated("wrapping operations should be done with the Wrapping traits. E.g: x.wrapping_mul(y)")]
pub fn wrapping_mul<T>(x: T, y: T) -> T
where
T: AsPrimitive<Field>,
Expand All @@ -117,46 +118,44 @@ where
pub fn as_witness(x: Field) {}

mod tests {
use super::wrapping_mul;
use super::ops::arith::WrappingMul;

#[test(should_fail_with = "custom message")]
fn test_static_assert_custom_message() {
super::static_assert(1 == 2, "custom message");
}

#[test(should_fail)]
#[test]
fn test_wrapping_mul() {
// This currently fails.
// See: https://github.com/noir-lang/noir/issues/7528
let zero: u128 = 0;
let one: u128 = 1;
let two_pow_64: u128 = 0x10000000000000000;
let u128_max: u128 = 0xffffffffffffffffffffffffffffffff;

// 1*0==0
assert_eq(zero, wrapping_mul(zero, one));
assert_eq(zero, zero.wrapping_mul(one));

// 0*1==0
assert_eq(zero, wrapping_mul(one, zero));
assert_eq(zero, one.wrapping_mul(zero));

// 1*1==1
assert_eq(one, wrapping_mul(one, one));
assert_eq(one, one.wrapping_mul(one));

// 0 * ( 1 << 64 ) == 0
assert_eq(zero, wrapping_mul(zero, two_pow_64));
assert_eq(zero, zero.wrapping_mul(two_pow_64));

// ( 1 << 64 ) * 0 == 0
assert_eq(zero, wrapping_mul(two_pow_64, zero));
assert_eq(zero, two_pow_64.wrapping_mul(zero));

// 1 * ( 1 << 64 ) == 1 << 64
assert_eq(two_pow_64, wrapping_mul(two_pow_64, one));
assert_eq(two_pow_64, two_pow_64.wrapping_mul(one));

// ( 1 << 64 ) * 1 == 1 << 64
assert_eq(two_pow_64, wrapping_mul(one, two_pow_64));
assert_eq(two_pow_64, one.wrapping_mul(two_pow_64));

// ( 1 << 64 ) * ( 1 << 64 ) == 1 << 64
assert_eq(zero, wrapping_mul(two_pow_64, two_pow_64));
assert_eq(zero, two_pow_64.wrapping_mul(two_pow_64));
// -1 * -1 == 1
assert_eq(one, wrapping_mul(u128_max, u128_max));
assert_eq(one, u128_max.wrapping_mul(u128_max));
}
}
267 changes: 267 additions & 0 deletions noir_stdlib/src/ops/arith.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::convert::AsPrimitive;

// docs:start:add-trait
pub trait Add {
fn add(self, other: Self) -> Self;
Expand Down Expand Up @@ -346,3 +348,268 @@ impl Neg for i64 {
}
}
// docs:end:neg-trait-impls

// docs:start:wrapping-add-trait
pub trait WrappingAdd {
fn wrapping_add(self, y: Self) -> Self;
}
// docs:end:wrapping-add-trait

impl WrappingAdd for u1 {
fn wrapping_add(self: u1, y: u1) -> u1 {
self ^ y
}
}

impl WrappingAdd for u8 {
fn wrapping_add(self: u8, y: u8) -> u8 {
wrapping_add_hlp(self, y)
}
}

impl WrappingAdd for u16 {
fn wrapping_add(self: u16, y: u16) -> u16 {
wrapping_add_hlp(self, y)
}
}

impl WrappingAdd for u32 {
fn wrapping_add(self: u32, y: u32) -> u32 {
wrapping_add_hlp(self, y)
}
}

impl WrappingAdd for u64 {
fn wrapping_add(self: u64, y: u64) -> u64 {
wrapping_add_hlp(self, y)
}
}

impl WrappingAdd for u128 {
fn wrapping_add(self: u128, y: u128) -> u128 {
wrapping_add_hlp(self, y)
}
}

impl WrappingAdd for i8 {
fn wrapping_add(self: i8, y: i8) -> i8 {
wrapping_add_hlp(self, y)
}
}

impl WrappingAdd for i16 {
fn wrapping_add(self: i16, y: i16) -> i16 {
wrapping_add_hlp(self, y)
}
}

impl WrappingAdd for i32 {
fn wrapping_add(self: i32, y: i32) -> i32 {
wrapping_add_hlp(self, y)
}
}

impl WrappingAdd for i64 {
fn wrapping_add(self: i64, y: i64) -> i64 {
wrapping_add_hlp(self, y)
}
}
impl WrappingAdd for Field {
fn wrapping_add(self: Field, y: Field) -> Field {
self + y
}
}

// docs:start:wrapping-sub-trait
pub trait WrappingSub {
fn wrapping_sub(self, y: Self) -> Self;
}
// docs:start:wrapping-sub-trait

impl WrappingSub for u1 {
fn wrapping_sub(self: u1, y: u1) -> u1 {
self ^ y
}
}

impl WrappingSub for u8 {
fn wrapping_sub(self: u8, y: u8) -> u8 {
wrapping_sub_hlp(self, y) as u8
}
}

impl WrappingSub for u16 {
fn wrapping_sub(self: u16, y: u16) -> u16 {
wrapping_sub_hlp(self, y) as u16
}
}

impl WrappingSub for u32 {
fn wrapping_sub(self: u32, y: u32) -> u32 {
wrapping_sub_hlp(self, y) as u32
}
}
impl WrappingSub for u64 {
fn wrapping_sub(self: u64, y: u64) -> u64 {
wrapping_sub_hlp(self, y) as u64
}
}
impl WrappingSub for u128 {
fn wrapping_sub(self: u128, y: u128) -> u128 {
wrapping_sub_hlp(self, y) as u128
}
}

impl WrappingSub for i8 {
fn wrapping_sub(self: i8, y: i8) -> i8 {
wrapping_sub_hlp(self, y) as i8
}
}

impl WrappingSub for i16 {
fn wrapping_sub(self: i16, y: i16) -> i16 {
wrapping_sub_hlp(self, y) as i16
}
}

impl WrappingSub for i32 {
fn wrapping_sub(self: i32, y: i32) -> i32 {
wrapping_sub_hlp(self, y) as i32
}
}
impl WrappingSub for i64 {
fn wrapping_sub(self: i64, y: i64) -> i64 {
wrapping_sub_hlp(self, y) as i64
}
}
impl WrappingSub for Field {
fn wrapping_sub(self: Field, y: Field) -> Field {
self - y
}
}

// docs:start:wrapping-mul-trait
pub trait WrappingMul {
fn wrapping_mul(self, y: Self) -> Self;
}
// docs:start:wrapping-mul-trait

impl WrappingMul for u1 {
fn wrapping_mul(self: u1, y: u1) -> u1 {
self & y
}
}

impl WrappingMul for u8 {
fn wrapping_mul(self: u8, y: u8) -> u8 {
wrapping_mul_hlp(self, y)
}
}

impl WrappingMul for u16 {
fn wrapping_mul(self: u16, y: u16) -> u16 {
wrapping_mul_hlp(self, y)
}
}

impl WrappingMul for u32 {
fn wrapping_mul(self: u32, y: u32) -> u32 {
wrapping_mul_hlp(self, y)
}
}
impl WrappingMul for u64 {
fn wrapping_mul(self: u64, y: u64) -> u64 {
wrapping_mul_hlp(self, y)
}
}

impl WrappingMul for i8 {
fn wrapping_mul(self: i8, y: i8) -> i8 {
wrapping_mul_hlp(self, y)
}
}

impl WrappingMul for i16 {
fn wrapping_mul(self: i16, y: i16) -> i16 {
wrapping_mul_hlp(self, y)
}
}

impl WrappingMul for i32 {
fn wrapping_mul(self: i32, y: i32) -> i32 {
wrapping_mul_hlp(self, y)
}
}

impl WrappingMul for i64 {
fn wrapping_mul(self: i64, y: i64) -> i64 {
wrapping_mul_hlp(self, y)
}
}

impl WrappingMul for u128 {
fn wrapping_mul(self: u128, y: u128) -> u128 {
wrapping_mul128_hlp(self, y)
}
}
impl WrappingMul for Field {
fn wrapping_mul(self: Field, y: Field) -> Field {
self * y
}
}

fn wrapping_add_hlp<T>(x: T, y: T) -> T
where
T: AsPrimitive<Field>,
Field: AsPrimitive<T>,
{
AsPrimitive::as_(x.as_() + y.as_())
}

fn wrapping_sub_hlp<T>(x: T, y: T) -> Field
where
T: AsPrimitive<Field>,
{
//340282366920938463463374607431768211456 is 2^128, it is used to avoid underflow
x.as_() + 340282366920938463463374607431768211456 - y.as_()
}

fn wrapping_mul_hlp<T>(x: T, y: T) -> T
where
T: AsPrimitive<Field>,
Field: AsPrimitive<T>,
{
AsPrimitive::as_(x.as_() * y.as_())
}

global two_pow_64: u128 = 0x10000000000000000;
/// Splits a 128 bits number into two 64 bits limbs
unconstrained fn split64(x: u128) -> (u64, u64) {
let lo = x as u64;
let hi = (x / two_pow_64) as u64;
(lo, hi)
}

/// Split a 128 bits number into two 64 bits limbs
/// It will fail if the number is more than 128 bits
fn split_into_64_bit_limbs(x: u128) -> (u64, u64) {
// Safety: the limbs are constrained below
let (x_lo, x_hi) = unsafe { split64(x) };
assert(x as Field == x_lo as Field + x_hi as Field * two_pow_64 as Field);
(x_lo, x_hi)
}

#[field(bn254)]
fn wrapping_mul128_hlp(x: u128, y: u128) -> u128 {
let (x_lo, x_hi) = split_into_64_bit_limbs(x);
let (y_lo, y_hi) = split_into_64_bit_limbs(y);
// Multiplication using the limbs:(x_lo + 2**64*x_hi)*(y_lo + 2**64*y_hi)=x_lo*y_lo+...
// and skipping the terms over 2**128
// Working with u64 limbs ensures that we cannot overflow the field modulus.
let low = x_lo as Field * y_lo as Field;
let lo = low as u64 as Field;
let carry = (low - lo) / two_pow_64 as Field;
let high = x_lo as Field * y_hi as Field + x_hi as Field * y_lo as Field + carry;
let hi = high as u64 as Field;
(lo + two_pow_64 as Field * hi) as u128
}
2 changes: 1 addition & 1 deletion noir_stdlib/src/ops/mod.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub(crate) mod arith;
pub(crate) mod bit;

pub use arith::{Add, Div, Mul, Neg, Rem, Sub};
pub use arith::{Add, Div, Mul, Neg, Rem, Sub, WrappingAdd, WrappingMul, WrappingSub};
pub use bit::{BitAnd, BitOr, BitXor, Not, Shl, Shr};
Loading
Loading