Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support for u128 #3913

Merged
merged 25 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion compiler/noirc_frontend/src/lexer/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub enum LexerErrorKind {
InvalidIntegerLiteral { span: Span, found: String },
#[error("{:?} is not a valid attribute", found)]
MalformedFuncAttribute { span: Span, found: String },
#[error("Integer type is larger than the maximum supported size of u127")]
#[error("Integer type is larger than the maximum supported size of u128")]
TooManyBits { span: Span, max: u32, got: u32 },
#[error("Logical and used instead of bitwise and")]
LogicalAnd { span: Span },
Expand Down
4 changes: 4 additions & 0 deletions compiler/noirc_frontend/src/lexer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,10 @@ impl IntType {

let max_bits = FieldElement::max_num_bits() / 2;

if !is_signed && str_as_u32 == 128 {
return Ok(Some(Token::Ident("U128".to_string())));
}

if str_as_u32 > max_bits {
return Err(LexerErrorKind::TooManyBits { span, max: max_bits, got: str_as_u32 });
}
Expand Down
1 change: 1 addition & 0 deletions noir_stdlib/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod cmp;
mod ops;
mod default;
mod prelude;
mod uint128;

// Oracle calls are required to be wrapped in an unconstrained function
// Thus, the only argument to the `println` oracle is expected to always be an ident
Expand Down
1 change: 1 addition & 0 deletions noir_stdlib/src/prelude.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::collections::vec::Vec;
use crate::option::Option;
use crate::{print, println, assert_constant};
use crate::uint128::U128;
use crate::cmp::{Eq, Ord};
use crate::default::Default;
242 changes: 242 additions & 0 deletions noir_stdlib/src/uint128.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
use crate::ops::{Add, Sub, Mul, Div, Rem, BitOr, BitAnd, BitXor};
use crate::cmp::{Eq, Ord, Ordering};
jfecher marked this conversation as resolved.
Show resolved Hide resolved

global pow64 : Field = 18446744073709551616; //2^64;

struct U128 {
lo: Field,
hi: Field,
}

impl U128 {

pub fn new(lo: u64, hi: u64) -> U128 {
// in order to handle multiplication, we need to represent the product of two u64 without overflow
assert(crate::field::modulus_num_bits() as u32 > 128);
U128 {
lo: lo as Field,
hi: hi as Field,
}
}

pub fn from_le_bytes(bytes: [u8; 16]) -> U128 {
let mut lo = 0;
let mut base = 1;
for i in 0..8 {
lo += (bytes[i] as Field)*base;
base *= 256;
}
let mut hi = 0;
base = 1;
for i in 8..16 {
hi += (bytes[i] as Field)*base;
base *= 256;
}
U128 {
lo,
hi,
}
}

pub fn to_le_bytes(self: Self) -> [u8; 16] {
let lo = self.lo.to_le_bytes(8);
let hi = self.hi.to_le_bytes(8);
let mut bytes = [0;16];
for i in 0..8 {
bytes[i] = lo[i];
bytes[i+8] = hi[i];
}
bytes
}

pub fn from_hex<N>(hex: str<N>) -> U128 {
let N = N as u32;
let bytes = hex.as_bytes();
// string must starts with "0x"
assert((bytes[0] == 48) & (bytes[1] == 120), "Invalid hexadecimal string");
assert(N < 35, "Input does not fit into a u128");

let mut lo = 0;
let mut hi = 0;
let mut base = 1;
if N <= 18 {
for i in 0..N-2 {
lo += U128::decode_ascii(bytes[N-i-1])*base;
base = base*16;
}
} else {
for i in 0..16 {
lo += U128::decode_ascii(bytes[N-i-1])*base;
base = base*16;
}
base = 1;
for i in 17..N-1 {
hi += U128::decode_ascii(bytes[N-i])*base;
base = base*16;
}
}
U128 {
lo: lo as Field,
hi: hi as Field,
}
}

fn decode_ascii(ascii: u8) -> Field {
if ascii < 58 {
ascii - 48
} else {
if ascii < 71 {
ascii - 55
} else {
ascii - 87
}

} as Field
}

unconstrained fn unconstrained_div(self: Self, b: U128) -> (U128, U128) {
if self < b {
(U128::new(0, 0), self)
} else {
//TODO check if this can overflow?
let (q,r) = self.unconstrained_div(b * U128::new(2,0));
let q_mul_2 = q * U128::new(2,0);
guipublic marked this conversation as resolved.
Show resolved Hide resolved
if r < b {
(q_mul_2, r)
} else {
(q_mul_2 + U128::new(1,0), r - b)
}

}
}

pub fn from_integer<T>(i: T) -> U128 {
let f = crate::as_field(i);
let lo = f as u64 as Field;
let hi = (f-lo) / pow64;
U128 {
lo,
hi,
}
}

pub fn to_integer<T>(self) -> T {
crate::from_field(self.lo+self.hi*pow64)
}
}

impl Add for U128 {
pub fn add(self: Self, b: U128) -> U128 {
let low = self.lo + b.lo;
let lo = low as u64 as Field;
let carry = (low - lo) / pow64;
let high = self.hi + b.hi + carry;
let hi = high as u64 as Field;
assert(hi == high, "attempt to add with overflow");
U128 {
lo,
hi,
}
}
}

impl Sub for U128 {
pub fn sub(self: Self, b: U128) -> U128 {
let low = pow64 + self.lo - b.lo;
let lo = low as u64 as Field;
let borrow = (low == lo) as Field;
let high = self.hi - b.hi - borrow;
let hi = high as u64 as Field;
assert(hi == high, "attempt to subtract with overflow");
U128 {
lo,
hi,
}
}
}

impl Mul for U128 {
pub fn mul(self: Self, b: U128) -> U128 {
assert(self.hi*b.hi == 0, "attempt to multiply with overflow");
let low = self.lo*b.lo;
let lo = low as u64 as Field;
let carry = (low - lo) / pow64;
let high = if crate::field::modulus_num_bits() as u32 > 196 {
(self.lo+self.hi)*(b.lo+b.hi) - low + carry
} else {
self.lo*b.hi + self.hi*b.lo + carry
};
let hi = high as u64 as Field;
assert(hi == high, "attempt to multiply with overflow");
U128 {
lo,
hi,
}
}
}

impl Div for U128 {
pub fn div(self: Self, b: U128) -> U128 {
let (q,r) = self.unconstrained_div(b);
let a = b * q + r;
assert_eq(self, a);
assert(r < b);
q
}
}

impl Rem for U128 {
pub fn rem(self: Self, b: U128) -> U128 {
let (q,r) = self.unconstrained_div(b);
let a = b * q + r;
assert_eq(self, a);
assert(r < b);
r
}
}

impl Eq for U128 {
pub fn eq(self: Self, b: U128) -> bool {
(self.lo == b.lo) & (self.hi == b.hi)
}
}

impl Ord for U128 {
fn cmp(self, other: Self) -> Ordering {
let hi_ordering = (self.hi as u64).cmp((other.hi as u64));
let lo_ordering = (self.lo as u64).cmp((other.lo as u64));

if hi_ordering == Ordering::equal() {
lo_ordering
} else {
hi_ordering
}
}
}

impl BitOr for U128 {
fn bitor(self, other: U128) -> U128 {
U128 {
lo: ((self.lo as u64) | (other.lo as u64)) as Field,
hi: ((self.hi as u64) | (other.hi as u64))as Field
}
}
}

impl BitAnd for U128 {
fn bitand(self, other: U128) -> U128 {
U128 {
lo: ((self.lo as u64) & (other.lo as u64)) as Field,
hi: ((self.hi as u64) & (other.hi as u64)) as Field
}
}
}

impl BitXor for U128 {
fn bitxor(self, other: U128) -> U128 {
U128 {
lo: ((self.lo as u64) ^ (other.lo as u64)) as Field,
hi: ((self.hi as u64) ^ (other.hi as u64)) as Field
}
}
}
6 changes: 6 additions & 0 deletions test_programs/execution_success/u128/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "u128"
type = "bin"
authors = [""]

[dependencies]
7 changes: 7 additions & 0 deletions test_programs/execution_success/u128/Prover.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
x = "3"
y = "4"
z = "7"
hexa ="0x1f03a"
[big_int]
lo = 1
hi = 2
38 changes: 38 additions & 0 deletions test_programs/execution_success/u128/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use dep::std;

fn main(mut x: u32, y: u32, z: u32, big_int: u128, hexa: str<7>) {
let a = u128::new(x as u64, x as u64);
let b = u128::new(y as u64, x as u64);
let c = a + b;
assert(c.lo == z as Field);
assert(c.hi == 2 * x as Field);
assert(u128::from_hex(hexa).lo == 0x1f03a);
let t1 = u128::from_hex("0x9d9c7a87771f03a23783f9d9c7a8777");
let t2 = u128::from_hex("0x45a26c708BFCF39041");
let t = t1 + t2;
assert(t.lo == 0xc5e4b029996e17b8);
assert(t.hi == 0x09d9c7a87771f07f);
let t3 = u128::from_le_bytes(t.to_le_bytes());
assert(t == t3);

let t4 = t - t2;
assert(t4 == t1);

let t5 = u128::new(0, 1);
let t6 = u128::new(1, 0);
assert((t5 - t6).hi == 0);

assert(
(u128::from_hex("0x71f03a23783f9d9c7a8777") * u128::from_hex("0x8BFCF39041")).hi
== u128::from_hex("0x3e4e0471b873470e247c824e61445537").hi
);
let q = u128::from_hex("0x3e4e0471b873470e247c824e61445537") / u128::from_hex("0x8BFCF39041");
assert(q == u128::from_hex("0x71f03a23783f9d9c7a8777"));

assert(big_int.hi == 2);

let small_int = u128::from_integer(x);
assert(small_int.lo == x as Field);
assert(x == small_int.to_integer());
}

Loading