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

u256 wide multiplication #2228

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
59ed5c7
Removing anti-freedom "Code of Conduct".
Genya-Z Feb 14, 2023
ee97e92
Merge branch 'main' of https://github.com/starkware-libs/cairo
Genya-Z Feb 15, 2023
0904b6a
Merge branch 'main' of https://github.com/starkware-libs/cairo
Genya-Z Feb 16, 2023
f2a6b75
Added u256_wide_mul.
Genya-Z Feb 16, 2023
fb10f87
Clarifying comment.
Genya-Z Feb 16, 2023
c1d8d4b
Merge remote-tracking branch 'origin/main'
Genya-Z Feb 16, 2023
d87d50f
Merge branch 'starkware-libs:main' into main
Genya-Z Feb 16, 2023
94b2bd9
Merge branch 'starkware-libs:main' into main
Genya-Z Feb 17, 2023
891e0a9
Added u128_to_u64s
Genya-Z Feb 17, 2023
6fd877b
Fixed mistake.
Genya-Z Feb 17, 2023
83e8d64
Adding u128_to_64s. Hopefully Dori can help fix this.
Genya-Z Feb 21, 2023
8ecb0c7
Removed dangling comment.
Genya-Z Feb 21, 2023
e89e744
Cleaned up type names.
Genya-Z Feb 21, 2023
76902f5
Cleared commented code.
Genya-Z Feb 21, 2023
3ba52c8
Merge remote-tracking branch 'upstream/main'
Genya-Z Feb 21, 2023
29d3208
Added edge cases.
Genya-Z Feb 21, 2023
b70b818
tabs -> spaces
Genya-Z Feb 21, 2023
180eccd
Merge branch 'starkware-libs:main' into main
Genya-Z Feb 22, 2023
cc53a37
Fixes.
Genya-Z Feb 23, 2023
6aa1190
Merge branch 'starkware-libs:main' into main
Genya-Z Feb 23, 2023
a93ed63
Fixed tabs.
Genya-Z Mar 7, 2023
6c17dc3
Merge remote-tracking branch 'upstream/main'
Genya-Z Mar 7, 2023
a96975b
Fixed formatting.
Genya-Z Mar 14, 2023
cf86740
Merge remote-tracking branch 'upstream/main'
Genya-Z Mar 14, 2023
7cb5448
Fixed formatting.
Genya-Z Mar 15, 2023
6b92bf3
Merge remote-tracking branch 'upstream/main'
Genya-Z Mar 15, 2023
313972d
Apparently felt is now felt252.
Genya-Z Mar 15, 2023
513a4c9
Fixed formating. Hopefully for the last time.
Genya-Z Mar 15, 2023
8a681bf
Merge remote-tracking branch 'upstream/main'
Genya-Z Mar 15, 2023
aa9c559
Adding comment.
Genya-Z Mar 17, 2023
ad83292
Changed order of outputs.
Genya-Z Mar 17, 2023
05da7f2
Merge remote-tracking branch 'upstream/main'
Genya-Z Mar 17, 2023
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
38 changes: 38 additions & 0 deletions corelib/src/integer.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ enum U128sFromFeltResult {
}
extern fn u128s_from_felt(a: felt) -> U128sFromFeltResult implicits(RangeCheck) nopanic;

extern fn u128_to_u64s(a: u128) -> (u64,u64) implicits(RangeCheck) nopanic;

#[panic_with('u128_from OF', u128_from_felt)]
fn u128_try_from_felt(a: felt) -> Option::<u128> implicits(RangeCheck) nopanic {
match u128s_from_felt(a) {
Expand Down Expand Up @@ -762,6 +764,42 @@ fn u256_overflow_mul(a: u256, b: u256) -> (u256, bool) {
(u256 { low, high }, overflow)
}


const HALF_SHIFT : felt = 18446744073709551616;//2^64;
fn u256_wide_mul(a: u256, b: u256) -> (u256, u256) implicits(RangeCheck) {
let (a0u, a1u) = u128_to_u64s(a.low);
let (a2u, a3u) = u128_to_u64s(a.high);
let (b0u, b1u) = u128_to_u64s(b.low);
let (b2u, b3u) = u128_to_u64s(b.high);
let a0 = u64_to_felt(a0u);
let a1 = u64_to_felt(a1u);
let a2 = u64_to_felt(a2u);
let a3 = u64_to_felt(a3u);
let b0 = u64_to_felt(b0u);
let b1 = u64_to_felt(b1u);
let b2 = u64_to_felt(b2u);
let b3 = u64_to_felt(b3u);

let B0 = b0*HALF_SHIFT;
let b12 = b1 + b2*HALF_SHIFT;
let b_low = u128_to_felt(b.low);
let b_high = u128_to_felt(b.high);

let res0 = u256_from_felt(a1 * B0 + a0 * b_low);
let res2 = u256_from_felt(
a3 * B0 + a2 * b_low + a1 * b12 + a0 * b_high + u128_to_felt(res0.high),
);
let res4 = u256_from_felt(
a3 * b12 + a2 * b_high + a1 * b3 + u128_to_felt(res2.high)
);

//guaranteed to fit in a u128, thus the check isn't necessary
//but felt_to_u128_unsafe doesn't exist
let res8 = u128_try_from_felt(a3 * b3 + u128_to_felt(res4.high)).unwrap();

(u256 {low: res0.low, high: res2.low}, u256 {low: res4.low, high: res8})
}

fn u256_checked_add(a: u256, b: u256) -> Option::<u256> implicits(RangeCheck) nopanic {
let (r, overflow) = u256_overflowing_add(a, b);
if overflow {
Expand Down
70 changes: 70 additions & 0 deletions corelib/src/test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,76 @@ fn test_u256_operators() {
as_u256(0_u128, 1_u128) * as_u256(0_u128, max_u128) == as_u256(0_u128, max_u128),
'1 * max_u128'
);

let(low,high)=integer::u256_wide_mul(as_u256(4_u128, 3_u128), as_u256(0_u128, 1_u128));
assert(
low == as_u256(4_u128, 3_u128),
'wide mul by 1 low'
);
assert(
high == as_u256(0_u128, 0_u128),
'wide mul by 1 high'
);
let(low,high)=integer::u256_wide_mul(as_u256(4_u128, 3_u128), as_u256(0_u128, 2_u128));
assert(
low == as_u256(8_u128, 6_u128),
'wide mul by 2 low'
);
assert(
high == as_u256(0_u128, 0_u128),
'wide mul by 2 high'
);
let(low,high)=integer::u256_wide_mul(as_u256(0_u128, pow_2_127()), as_u256(0_u128, 2_u128));
assert(
low == as_u256(1_u128, 0_u128),
'wide mul by OF low'
);
assert(
high == as_u256(0_u128, 0_u128),
'wide mul by OF high'
);
let(low,high)=integer::u256_wide_mul(as_u256(max_u128, max_u128), as_u256(max_u128, max_u128));
assert(
low == as_u256(0_u128, 1_u128),
'wide max_u256 * max_u256 low'
);
assert(
high == as_u256(max_u128, 0xfffffffffffffffffffffffffffffffe_u128),
'wide max_u256 * max_u256 high'
);
let(low,high)=integer::u256_wide_mul(as_u256(0_u128, 1_u128), as_u256(max_u128, max_u128));
assert(
low == as_u256(max_u128, max_u128),
'wide 1 * max_u256 low'
);
assert(
high == as_u256(0_u128, 0_u128),
'wide 1 * max_u256 high'
);
let(low,high)=integer::u256_wide_mul(as_u256(1_u128, 0_u128), as_u256(max_u128, max_u128));
assert(
low == as_u256(0_u128, 0_u128),
'wide 2^128 * max_u256 low'
);
assert(
high == as_u256(max_u128, max_u128),
'wide 2^128 * max_u256 high'
);
let(low,high)=integer::u256_wide_mul(as_u256(155419417030398358529415680970430503750_u128,
208595563450721923828867081157420252200_u128),
as_u256(285431191531813133557775306831253175872_u128,
43439607001498238463885217561246358334_u128));
assert(
low == as_u256(74008176751363765864810996693396202398_u128,
113989470359884732637183370837798242736_u128),
'wide mul low'
);
assert(
high == as_u256(130366876754661164843311819125622077435_u128,
238310065584501807926300814868835410406_u128),
'wide mul high'
);

assert(
(as_u256(1_u128, 2_u128) | as_u256(2_u128, 2_u128)) == as_u256(3_u128, 2_u128),
'1.2|2.2==3.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ pub fn core_libfunc_ap_change<InfoProvider: InvocationApChangeInfoProvider>(
}
},
Uint128Concrete::Divmod(_) => vec![ApChange::Known(7)],
Uint128Concrete::Split(_) => vec![ApChange::Known(0)],
Uint128Concrete::WideMul(_) => vec![ApChange::Known(17)],
Uint128Concrete::LessThan(_) => vec![ApChange::Known(2), ApChange::Known(3)],
Uint128Concrete::Equal(_) => vec![ApChange::Known(1), ApChange::Known(1)],
Expand Down
3 changes: 3 additions & 0 deletions crates/cairo-lang-sierra-gas/src/core_libfunc_cost_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,9 @@ fn u128_libfunc_cost<Ops: CostOperations>(
Uint128Concrete::Divmod(_) => {
vec![ops.const_cost(ConstCost { steps: 11, holes: 0, range_checks: 4 })]
}
Uint128Concrete::Split(_) => {
vec![ops.const_cost(ConstCost { steps: 0, holes: 0, range_checks: 3 })]
}
Uint128Concrete::WideMul(_) => {
vec![ops.const_cost(ConstCost { steps: 23, holes: 0, range_checks: 9 })]
}
Expand Down
42 changes: 42 additions & 0 deletions crates/cairo-lang-sierra-to-casm/src/invocations/uint128.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub fn build(
IntOperator::OverflowingSub => build_u128_overflowing_sub(builder),
},
Uint128Concrete::Divmod(_) => build_u128_divmod(builder),
Uint128Concrete::Split(_) => build_u128_to_u64s(builder),
Uint128Concrete::WideMul(_) => build_u128_widemul(builder),
Uint128Concrete::IsZero(_) => misc::build_is_zero(builder),
Uint128Concrete::Const(libfunc) => super::uint::build_const(libfunc, builder),
Expand Down Expand Up @@ -181,6 +182,47 @@ fn build_u128_divmod(
))
}

/// Splits a u128 into two u64's.
fn build_u128_to_u64s(
builder: CompiledInvocationBuilder<'_>,
) -> Result<CompiledInvocation, InvocationError> {
let [range_check, a] = builder.try_get_single_cells()?;
let max_u64: BigInt = BigInt::from(u64::MAX); // = 2**64 - 1.
let u64_bound: BigInt = BigInt::from(u64::MAX) + 1; // = 2**64.
let mut casm_builder = CasmBuilder::default();
add_input_variables! {casm_builder,
buffer(2) range_check;
deref_or_immediate a;
};
casm_build_extend! {casm_builder,
let orig_range_check = range_check;
const u64_limit = u64_bound.clone();
const m_u64 = max_u64.clone();
tempvar high;
tempvar low;
tempvar rced_value;
tempvar h_2_64;
hint DivMod { lhs: a, rhs: u64_limit } into { quotient: high, remainder: low };
// Write value as 2**64 * high + low.
assert high = *(range_check++);
assert low = *(range_check++);
// Verify `low < 2**64` by constraining `0 <= (2**64-1) - low`.
assert rced_value = m_u64 - low;
assert rced_value = *(range_check++);
// Check that value = 2**128 * x + y (mod PRIME).
assert h_2_64 = high * u64_limit;
assert a = h_2_64 + low;
};
Ok(builder.build_from_casm_builder(
casm_builder,
[("Fallthrough", &[&[range_check], &[low], &[high]], None)],
CostValidationInfo {
range_check_info: Some((orig_range_check, range_check)),
extra_costs: None,
},
))
}

/// Handles a u128 overflowing widemul operation.
fn build_u128_widemul(
builder: CompiledInvocationBuilder<'_>,
Expand Down
44 changes: 43 additions & 1 deletion crates/cairo-lang-sierra/src/extensions/modules/uint128.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::range_check::RangeCheckType;
use super::uint::{
IntOperator, UintConstLibfunc, UintDivmodLibfunc, UintEqualLibfunc, UintLessThanLibfunc,
UintLessThanOrEqualLibfunc, UintOperationConcreteLibfunc, UintOperationLibfunc,
UintSquareRootLibfunc, UintToFeltLibfunc, UintTraits, UintType,
UintSquareRootLibfunc, UintToFeltLibfunc, UintTraits, UintType, Uint64Type,
};
use crate::define_libfunc_hierarchy;
use crate::extensions::lib_func::{
Expand All @@ -25,6 +25,7 @@ define_libfunc_hierarchy! {
pub enum Uint128Libfunc {
Operation(UintOperationLibfunc<Uint128Traits>),
Divmod(UintDivmodLibfunc<Uint128Traits>),
Split(Uint128ToUint64sLibfunc),
WideMul(Uint128WideMulLibfunc),
LessThan(UintLessThanLibfunc<Uint128Traits>),
Equal(UintEqualLibfunc<Uint128Traits>),
Expand Down Expand Up @@ -152,6 +153,47 @@ impl GenericLibfunc for Uint128OperationLibfunc {
}
}

/// Libfunc for splitting u128.
#[derive(Default)]
pub struct Uint128ToUint64sLibfunc {}
impl NoGenericArgsGenericLibfunc for Uint128ToUint64sLibfunc {
const STR_ID: &'static str = "u128_to_u64s";

fn specialize_signature(
&self,
context: &dyn SignatureSpecializationContext,
) -> Result<LibfuncSignature, SpecializationError> {
let ty_u128 = context.get_concrete_type(Uint128Type::id(), &[])?;
let ty_u64 = context.get_concrete_type(Uint64Type::id(), &[])?;
let range_check_type = context.get_concrete_type(RangeCheckType::id(), &[])?;
Ok(LibfuncSignature::new_non_branch_ex(
vec![
ParamSignature {
ty: range_check_type.clone(),
allow_deferred: false,
allow_add_const: true,
allow_const: false,
},
ParamSignature::new(ty_u128),
],
vec![
OutputVarInfo {
ty: range_check_type,
ref_info: OutputVarReferenceInfo::Deferred(DeferredOutputKind::AddConst {
param_idx: 0,
}),
},
OutputVarInfo {
ty: ty_u64.clone(),
ref_info: OutputVarReferenceInfo::NewTempVar { idx: Some(0) },
},
OutputVarInfo { ty: ty_u64, ref_info: OutputVarReferenceInfo::NewTempVar { idx: Some(1) } },
],
SierraApChange::Known { new_vars_only: false },
))
}
}

/// Libfunc for u128 wide mul.
#[derive(Default)]
pub struct Uint128WideMulLibfunc {}
Expand Down
14 changes: 14 additions & 0 deletions crates/cairo-lang-sierra/src/simulation/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,20 @@ fn simulate_u128_libfunc(
[_, _, _] => Err(LibfuncSimulationError::MemoryLayoutMismatch),
_ => Err(LibfuncSimulationError::WrongNumberOfArgs),
},
Uint128Concrete::Split(_) => match inputs {
[CoreValue::RangeCheck, CoreValue::Uint128(x)] => {
Ok((
vec![
CoreValue::RangeCheck,
CoreValue::Uint64((x % 1<<64).try_into().unwrap()),
CoreValue::Uint64((x / 1<<64).try_into().unwrap()),
],
0,
))
}
[_, _] => Err(LibfuncSimulationError::MemoryLayoutMismatch),
_ => Err(LibfuncSimulationError::WrongNumberOfArgs),
},
Uint128Concrete::WideMul(_) => match inputs {
[CoreValue::RangeCheck, CoreValue::Uint128(lhs), CoreValue::Uint128(rhs)] => {
let result = BigInt::from(*lhs) * BigInt::from(*rhs);
Expand Down
45 changes: 0 additions & 45 deletions docs/CODE_OF_CONDUCT.md

This file was deleted.