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 33 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::*;
310 changes: 310 additions & 0 deletions sway-lib-std/src/u128.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
library u128;

use core::num::*;
use ::assert::assert;
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(h: u64, l: u64) -> Self;
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
} {
fn into(v: U128) -> (u64, u64) {
(v.upper, v.lower)
}
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
}

impl From for U128 {
pub fn from(h: u64, l: u64) -> U128 {
U128 {
upper: h,
lower: l,
}
}
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
}

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() {
asm(r1) {
movi r1 i3;
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
flag r1;
}
}

fn enable_overflow() {
asm(r1) {
movi r1 i0;
flag r1;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these belong somewhere more general as pub definitions? Like num or somewhere?

Copy link
Contributor

@nfurfaro nfurfaro May 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm. My understanding was that the flag op had to be used in the same asm block as the op you're trying to allow overflow for, otherwise, the bit is unset after the block returns. Maybe a misunderstanding on my part, perhaps I was confused with $of being cleared after an operation.
Is this working?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, perhaps rather than movi r1 i3; here you could initialize a second register with the value as a named variable (or even better, as a const) to:
a.) help document the function better, and
b.) to follow the general guideline of "if it can be done outside of asm, do it outside of asm".

ie:

const WRAPPING_FLAG_VALUE = 3;
asm(r1: WRAPPING_FLAG_VALUE) {
 flag r1;
}

Copy link
Contributor

@nfurfaro nfurfaro May 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And finally, it seems that the flag values for enable and disable are reversed.
An unset flag (ie: 0) should be the default state in which panic occurs on overflow.
Setting to a non-zero value should enable overflow.
Edit: I think it's actually just the names that seem reversed to me. like, disable_overflow should be disable_panic_on_overflow, and enable_overflow should be enable_panic_on_overflow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, perhaps rather than movi r1 i3; here you could initialize a second register with the value as a named variable (or even better, as a const) to: a.) help document the function better, and b.) to follow the general guideline of "if it can be done outside of asm, do it outside of asm".

ie:

const WRAPPING_FLAG_VALUE = 3;
asm(r1: WRAPPING_FLAG_VALUE) {
 flag r1;
}

What about

asm(WRAPPING_FLAG_VALUE: 3) {
    flag WRAPPING_FLAG_VALUE;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a combination of a descriptively-named register and a const value would be best, and in line with tx.sw and other modules where we use named const values to initialize registers with.

Copy link
Contributor

@nfurfaro nfurfaro May 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these belong somewhere more general as pub definitions? Like num or somewhere?

We had talked about a stdlib module for wrapping VM opcodes, this would probably fit the bill. Will look for the open issue.
#1074
Perhaps I'll just add a task to that issue to create a new module and extract these in a separate PR.
Done.
cc @otrho

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 36f5ef7. Filed #1664 to follow-up on cleaner constants.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@otrho given that no other part of the stdlib needs these functions, can we defer it to a future PR?


impl u64 {
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
pub fn overflowing_add(a: u64, b: u64) -> U128 {
disable_overflow();
let mut v = U128 {
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
upper: 0,
lower: 0,
};
asm(r1, r2, a: a, b: b, v_ptr: v) {
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
add r1 a b;
move r2 of;
sw v_ptr r2 i0;
sw v_ptr r1 i1;
};
enable_overflow();
v
}

pub fn overflowing_mul(self, b: Self) -> U128 {
disable_overflow();
let mut v = U128 {
upper: 0,
lower: 0,
};
asm(r1, r2, a: self, b: b, v_ptr: v) {
mul r1 a b;
move r2 of;
sw v_ptr r2 i0;
sw v_ptr r1 i1;
};
enable_overflow();
v
}
}

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, other: u64) -> Self {
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
// If shifting by at least the number of bits, then saturate with
// zeroes.
if (other >= 128) {
return ~Self::new();
};

// If shifting by at least half the number of bits, then upper word can
// be discarded.
if (other >= 64) {
return ~Self::from(self.lower << (other - 64), 0);
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
};

// 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 - other);

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

~Self::from(upper, lower)
}

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

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

let upper = self.upper >> other;
let lower = (self.lower >> other) + 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: upper,
lower: lower,
}
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
}
}

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();
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();
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" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
script;

use std::assert::assert;
use std::result::*;
use std::u128::*;
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved

fn main() -> bool {
let zero = ~U128::from(0, 0);
let one = ~U128::from(0, 1);
let two = ~U128::from(0, 2);
let max_u64 = ~U128::from(0, ~u64::max());
let one_upper = ~U128::from(1, 0);

let div_max_two = max_u64 / two;
assert(div_max_two.upper == 0);
assert(div_max_two.lower == ~u64::max() >> 1);

// Product of u64::MAX and u64::MAX.
let dividend = ~U128::from(~u64::max() - 1, 1);
let div_max_max = dividend / max_u64;
assert(div_max_max.upper == 0);
assert(div_max_max.lower == ~u64::max());

true
}
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_mul_test_pass'
dependencies = ['std']
Loading