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

Add U128 type #1600

Merged
merged 39 commits into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f3c2e53
u128 add and sub
May 6, 2022
974dde9
placeholders
May 6, 2022
50e1188
addition carry correction
May 6, 2022
16c118c
u64 multiplication
May 9, 2022
9d6796f
fix off by one in add
May 10, 2022
6435308
u64mul working with explicit upper/lower functions
May 14, 2022
603f437
functions for add/subtract of U128 and U64
May 14, 2022
2437209
comment blockers
May 15, 2022
0cc9e93
struct return param bug repro
May 15, 2022
bea85f6
Merge branch 'master' into adlerjohn/u128
adlerjohn May 18, 2022
136362f
Merge branch 'master' into adlerjohn/u128
adlerjohn May 19, 2022
c378550
Merge branch 'master' into adlerjohn/u128
adlerjohn May 19, 2022
ce5534d
Merge branch 'master' into adlerjohn/u128
adlerjohn May 19, 2022
1b70ec7
the rest of the fucking owl (mostly)
adlerjohn May 19, 2022
2a68d1d
Merge branch 'master' into adlerjohn/u128
adlerjohn May 24, 2022
680ad16
Add left and right shift ops.
adlerjohn May 25, 2022
e0720b2
Add test and fix.
adlerjohn May 25, 2022
e64d83e
Add binary mul and divide.
adlerjohn May 25, 2022
0d35b9d
Fix shift.
adlerjohn May 25, 2022
ec7d94f
Reorganize order and fixes.
adlerjohn May 25, 2022
f2c34fa
Remove sdk harness test.
adlerjohn May 25, 2022
93cd3b3
fixes
adlerjohn May 25, 2022
0a3c2a6
Add e2e test.
adlerjohn May 25, 2022
a6b6e7f
Add advanced test and refactor.
adlerjohn May 25, 2022
4f865bf
optimize mul and re-enable test now that there's sufficient gas
adlerjohn May 25, 2022
2844a1d
Add failing div test and clean up.
adlerjohn May 25, 2022
e91d917
Rename to mul test.
adlerjohn May 25, 2022
82325e7
Update lockfile.
adlerjohn May 25, 2022
de6fd09
Remove unneeded code.
adlerjohn May 25, 2022
ec90475
Fix bug with shift.
adlerjohn May 25, 2022
d552adf
Add div tests.
adlerjohn May 25, 2022
dc528df
Merge branch 'master' into adlerjohn/u128
adlerjohn May 25, 2022
bc25395
Actually remove from sdk harness.
adlerjohn May 25, 2022
3aaa6fd
Clean up naming.
adlerjohn May 25, 2022
b4bd182
Document.
adlerjohn May 25, 2022
b087157
Docstrings instead of comments.
adlerjohn May 25, 2022
36f5ef7
Improve documentation on flags.
adlerjohn May 25, 2022
3e29ad0
Document issue todo.
adlerjohn May 25, 2022
0602360
Document overflowing u64 ops.
adlerjohn May 25, 2022
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
1 change: 1 addition & 0 deletions sway-lib-std/src/lib.sw
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ dep token;
dep ecr;
dep reentrancy;
dep vm/mod;
dep u128;

use core::*;
337 changes: 337 additions & 0 deletions sway-lib-std/src/u128.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
library u128;

use core::num::*;
use ::assert::assert;
use ::context::registers::flags;
use ::result::Result;

/// The 128-bit unsigned integer type.
/// Represented as two 64-bit components: `(upper, lower)`, where `value = (upper << 64) + lower`.
pub struct U128 {
upper: u64,
lower: u64,
}

pub trait From {
/// Function for creating U128 from its u64 components.
pub fn from(upper: u64, lower: u64) -> Self;
} {
fn into(val: U128) -> (u64, u64) {
(val.upper, val.lower)
}
}

impl From for U128 {
pub fn from(upper: u64, lower: u64) -> U128 {
U128 {
upper, lower,
}
}
}

impl core::ops::Eq for U128 {
pub fn eq(self, other: Self) -> bool {
self.lower == other.lower && self.upper == other.upper
}
}

impl core::ops::Ord for U128 {
pub fn gt(self, other: Self) -> bool {
self.upper > other.upper || self.upper == other.upper && self.lower > other.lower
}

pub fn lt(self, other: Self) -> bool {
self.upper < other.upper || self.upper == other.upper && self.lower < other.lower
}
}

// TODO this doesn't work?
// impl core::ops::OrdEq for U128 {
// }
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved

fn disable_overflow() {
// Mask second bit, which is `F_WRAPPING`.
// TODO can't use binary literal: https://github.com/FuelLabs/sway/issues/1664
// 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000010
let mask = 2;
// Get the current value of the flags register and mask it, setting the
// masked bit. Flags are inverted, so set = off.
let flag_val = flags() | mask;
asm(flag_val: flag_val) {
flag flag_val;
}
}

fn enable_overflow() {
// Mask second bit, which is `F_WRAPPING`.
// TODO can't use binary literal: https://github.com/FuelLabs/sway/issues/1664
// 0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111101
let mask = 18446744073709551613;
// Get the current value of the flags register and mask it, unsetting the
// masked bit. Flags are inverted, so unset = on.
let flag_val = flags() & mask;
asm(flag_val: flag_val) {
flag flag_val;
}
}

impl u64 {
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
pub fn overflowing_add(self, right: Self) -> U128 {
disable_overflow();
let mut result = U128 {
upper: 0,
lower: 0,
};
asm(sum, overflow, left: self, right: right, result_ptr: result) {
// Add left and right.
add sum left right;
// Immediately copy the overflow of the addition from `$of` into
// `overflow` so that it's not lost.
move overflow of;
// Store the overflow into the first word of result.
sw result_ptr overflow i0;
// Store the sum into the second word of result.
sw result_ptr sum i1;
};
enable_overflow();
result
}

pub fn overflowing_mul(self, right: Self) -> U128 {
disable_overflow();
let mut result = U128 {
upper: 0,
lower: 0,
};
asm(product, overflow, left: self, right: right, result_ptr: result) {
// Multiply left and right.
mul product left right;
// Immediately copy the overflow of the multiplication from `$of` into
// `overflow` so that it's not lost.
move overflow of;
// Store the overflow into the first word of result.
sw result_ptr overflow i0;
// Store the product into the second word of result.
sw result_ptr product i1;
};
enable_overflow();
result
}
}

impl U128 {
/// Initializes a new, zeroed U128.
pub fn new() -> U128 {
U128 {
upper: 0,
lower: 0,
}
}

/// Downcast to `u64`. Err if precision would be lost, Ok otherwise.
pub fn to_u64(self) -> Result<u64, ()> {
match self.upper {
0 => {
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
Result::Ok(self.lower)
},
_ => {
Result::Err(())
},
}
}

/// The smallest value that can be represented by this integer type.
pub fn min() -> U128 {
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
U128 {
upper: 0,
lower: 0,
}
}

/// The largest value that can be represented by this type,
/// 2<sup>128</sup> - 1.
pub fn max() -> U128 {
U128 {
upper: ~u64::max(),
lower: ~u64::max(),
}
}

/// The size of this type in bits.
pub fn bits() -> u32 {
128
}
}

impl core::ops::BitwiseAnd for U128 {
pub fn binary_and(self, other: Self) -> Self {
~U128::from(self.upper & other.upper, self.lower & other.lower)
}
}

impl core::ops::BitwiseOr for U128 {
pub fn binary_or(self, other: Self) -> Self {
~U128::from(self.upper | other.upper, self.lower | other.lower)
}
}

impl core::ops::Shiftable for U128 {
pub fn lsh(self, rhs: u64) -> Self {
// If shifting by at least the number of bits, then saturate with
// zeroes.
if (rhs >= 128) {
return ~Self::new();
};

// If shifting by at least half the number of bits, then upper word can
// be discarded.
if (rhs >= 64) {
return ~Self::from(self.lower << (rhs - 64), 0);
};

// If shifting by less than half the number of bits, then need to
// partially shift both upper and lower.

// Save highest bits of lower half.
let highest_lower_bits = self.lower >> (64 - rhs);

let upper = (self.upper << rhs) + highest_lower_bits;
let lower = self.lower << rhs;

~Self::from(upper, lower)
}

pub fn rsh(self, rhs: u64) -> Self {
// If shifting by at least the number of bits, then saturate with
// zeroes.
if (rhs >= 128) {
return ~Self::new();
};

// If shifting by at least half the number of bits, then lower word can
// be discarded.
if (rhs >= 64) {
return ~Self::from(0, self.upper >> (rhs - 64));
};

// If shifting by less than half the number of bits, then need to
// partially shift both upper and lower.

// Save lowest bits of upper half.
let lowest_upper_bits = self.upper << (64 - rhs);

let upper = self.upper >> rhs;
let lower = (self.lower >> rhs) + lowest_upper_bits;

~Self::from(upper, lower)
}
}

impl core::ops::Add for U128 {
/// Add a U128 to a U128. Panics on overflow.
pub fn add(self, other: Self) -> Self {
let mut upper_128 = self.upper.overflowing_add(other.upper);

// If the upper overflows, then the number cannot fit in 128 bits, so panic.
assert(upper_128.upper == 0);
let lower_128 = self.lower.overflowing_add(other.lower);

// If overflow has occurred in the lower component addition, carry.
// Note: carry can be at most 1.
if lower_128.upper > 0 {
upper_128 = upper_128.lower.overflowing_add(lower_128.upper);
};

// If overflow has occurred in the upper component addition, panic.
assert(upper_128.upper == 0);

U128 {
upper: upper_128.lower,
lower: lower_128.lower,
}
}
}

impl core::ops::Subtract for U128 {
/// Subtract a U128 from a U128. Panics of overflow.
pub fn subtract(self, other: Self) -> Self {
// If trying to subtract a larger number, panic.
assert(!(self < other));

let mut upper = self.upper - other.upper;
let mut lower = 0;

// If necessary, borrow and carry for lower subtraction
if self.lower < other.lower {
lower = ~u64::max() - (other.lower - self.lower - 1);
upper = upper - 1;
} else {
lower = self.lower - other.lower;
};

U128 {
upper, lower,
}
}
}

impl core::ops::Multiply for U128 {
/// Multiply a U128 with a U128. Panics of overflow.
pub fn multiply(self, other: Self) -> Self {
let zero = ~U128::from(0, 0);
let one = ~U128::from(0, 1);

let mut total = ~U128::new();
// The algorithm loops <from number of bits - 1> to <zero>.
// Need to add 1 here to invalidate the while loop once i == 0 since we
// don't have a break keyword.
let mut i = 128 - 1 + 1;
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved

while i > 0 {
// Workaround for not having break keyword
let shift = i - 1;
total = total << 1;
if (other & (one << shift)) != zero {
total = total + self;
}

i = i - 1;
}
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved

total
}
}

impl core::ops::Divide for U128 {
/// Divide a U128 by a U128. Panics if divisor is zero.
pub fn divide(self, divisor: Self) -> Self {
let zero = ~U128::from(0, 0);
let one = ~U128::from(0, 1);

assert(divisor != zero);

let mut quotient = ~U128::new();
let mut remainder = ~U128::new();
// The algorithm loops <from number of bits - 1> to <zero>.
// Need to add 1 here to invalidate the while loop once i == 0 since we
// don't have a break keyword.
let mut i = 128 - 1 + 1;
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved

while i > 0 {
// Workaround for not having break keyword
let shift = i - 1;
quotient = quotient << 1;
remainder = remainder << 1;
remainder = remainder | ((self & (one << shift)) >> shift);
// TODO use >= once OrdEq can be implemented.
if remainder > divisor || remainder == divisor {
remainder = remainder - divisor;
quotient = quotient | one;
}

i = i - 1;
}

quotient
}
}
3 changes: 3 additions & 0 deletions test/src/e2e_vm_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ pub fn run(filter_regex: Option<regex::Regex>) {
ProgramState::Return(1), // true
),
("should_pass/stdlib/ge_test", ProgramState::Return(1)), // true
("should_pass/stdlib/u128_test", ProgramState::Return(1)), // true
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
("should_pass/stdlib/u128_div_test", ProgramState::Return(1)), // true
("should_pass/stdlib/u128_mul_test", ProgramState::Return(1)), // true
(
"should_pass/language/generic_structs",
ProgramState::Return(1), // true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
out
target
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[[package]]
name = 'core'
dependencies = []

[[package]]
name = 'std'
dependencies = ['core']

[[package]]
name = 'u128_div_test_pass'
dependencies = ['std']
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "u128_div_test_pass"

[dependencies]
std = { path = "../../../../../../../sway-lib-std" }
Loading