Skip to content

Commit

Permalink
feat: add native u128 type (#7301)
Browse files Browse the repository at this point in the history
Co-authored-by: kashbrti <kashbrti@gmail.com>
Co-authored-by: Ary Borenszweig <asterite@gmail.com>
  • Loading branch information
3 people authored Feb 20, 2025
1 parent c63cc73 commit 8783e48
Show file tree
Hide file tree
Showing 32 changed files with 593 additions and 47 deletions.
29 changes: 17 additions & 12 deletions compiler/noirc_evaluator/src/acir/acir_variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,8 @@ impl<F: AcirField, B: BlackBoxFunctionSolver<F>> AcirContext<F, B> {
pub(crate) fn not_var(&mut self, x: AcirVar, typ: AcirType) -> Result<AcirVar, RuntimeError> {
let bit_size = typ.bit_size::<F>();
// Subtracting from max flips the bits
let max = self.add_constant((1_u128 << bit_size) - 1);
let max = power_of_two::<F>(bit_size) - F::one();
let max = self.add_constant(max);
self.sub_var(max, x)
}

Expand Down Expand Up @@ -908,19 +909,9 @@ impl<F: AcirField, B: BlackBoxFunctionSolver<F>> AcirContext<F, B> {
self.assert_eq_var(lhs_constraint, rhs_constraint, None)?;

// Avoids overflow: 'q*b+r < 2^max_q_bits*2^max_rhs_bits'
let mut avoid_overflow = false;
if max_q_bits + max_rhs_bits >= F::max_num_bits() - 1 {
// q*b+r can overflow; we avoid this when b is constant
if rhs_expr.is_const() {
avoid_overflow = true;
} else {
// we do not support unbounded division
unreachable!("overflow in unbounded division");
}
}

if let Some(rhs_const) = rhs_expr.to_const() {
if avoid_overflow {
if let Some(rhs_const) = rhs_expr.to_const() {
// we compute q0 = p/rhs
let rhs_big = BigUint::from_bytes_be(&rhs_const.to_be_bytes());
let q0_big = F::modulus() / &rhs_big;
Expand All @@ -944,6 +935,20 @@ impl<F: AcirField, B: BlackBoxFunctionSolver<F>> AcirContext<F, B> {
predicate,
rhs_const.num_bits(),
)?;
} else if bit_size == 128 {
// q and b are u128 and q*b could overflow so we check that either q or b are less than 2^64
let two_pow_64: F = power_of_two(64);
let two_pow_64 = self.add_constant(two_pow_64);

let (q_upper, _) =
self.euclidean_division_var(quotient_var, two_pow_64, bit_size, predicate)?;
let (rhs_upper, _) =
self.euclidean_division_var(rhs, two_pow_64, bit_size, predicate)?;
let mul_uppers = self.mul_var(q_upper, rhs_upper)?;
self.assert_eq_var(mul_uppers, zero, None)?;
} else {
// we do not support unbounded division
unreachable!("overflow in unbounded division");
}
}

Expand Down
19 changes: 0 additions & 19 deletions compiler/noirc_evaluator/src/acir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1973,26 +1973,7 @@ impl<'a> Context<'a> {
) -> Result<AcirVar, RuntimeError> {
let lhs = self.convert_numeric_value(binary.lhs, dfg)?;
let rhs = self.convert_numeric_value(binary.rhs, dfg)?;

let binary_type = self.type_of_binary_operation(binary, dfg);
match &binary_type {
Type::Numeric(NumericType::Unsigned { bit_size })
| Type::Numeric(NumericType::Signed { bit_size }) => {
// Conservative max bit size that is small enough such that two operands can be
// multiplied and still fit within the field modulus. This is necessary for the
// truncation technique: result % 2^bit_size to be valid.
let max_integer_bit_size = FieldElement::max_num_bits() / 2;
if *bit_size > max_integer_bit_size {
return Err(RuntimeError::UnsupportedIntegerSize {
num_bits: *bit_size,
max_num_bits: max_integer_bit_size,
call_stack: self.acir_context.get_call_stack(),
});
}
}
_ => {}
}

let binary_type = AcirType::from(binary_type);
let bit_count = binary_type.bit_size::<FieldElement>();
let num_type = binary_type.to_numeric_type();
Expand Down
1 change: 1 addition & 0 deletions compiler/noirc_evaluator/src/ssa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ fn optimize_all(builder: SsaBuilder, options: &SsaEvaluatorOptions) -> Result<Ss
.run_pass(Ssa::remove_enable_side_effects, "EnableSideEffectsIf removal")
.run_pass(Ssa::fold_constants_using_constraints, "Constraint Folding")
.run_pass(Ssa::make_constrain_not_equal_instructions, "Adding constrain not equal")
.run_pass(Ssa::check_u128_mul_overflow, "Check u128 mul overflow")
.run_pass(Ssa::dead_instruction_elimination, "Dead Instruction Elimination (1st)")
.run_pass(Ssa::simplify_cfg, "Simplifying (3rd):")
.run_pass(Ssa::array_set_optimization, "Array Set Optimizations")
Expand Down
8 changes: 5 additions & 3 deletions compiler/noirc_evaluator/src/ssa/ir/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use binary::truncate_field;
use binary::{truncate, truncate_field};
use serde::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};

Expand Down Expand Up @@ -911,8 +911,10 @@ impl Instruction {
// would be incorrect however since the extra bits on the field would not be flipped.
Value::NumericConstant { constant, typ } if typ.is_unsigned() => {
// As we're casting to a `u128`, we need to clear out any upper bits that the NOT fills.
let value = !constant.to_u128() % (1 << typ.bit_size());
SimplifiedTo(dfg.make_constant(value.into(), *typ))
let bit_size = typ.bit_size();
assert!(bit_size <= 128);
let not_value: u128 = truncate(!constant.to_u128(), bit_size);
SimplifiedTo(dfg.make_constant(not_value.into(), *typ))
}
Value::Instruction { instruction, .. } => {
// !!v => v
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ fn convert_signed_integer_to_field_element(int: i128, bit_size: u32) -> FieldEle
}

/// Truncates `int` to fit within `bit_size` bits.
fn truncate(int: u128, bit_size: u32) -> u128 {
pub(super) fn truncate(int: u128, bit_size: u32) -> u128 {
if bit_size == 128 {
int
} else {
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_evaluator/src/ssa/ir/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl NumericType {
) -> Option<String> {
match self {
NumericType::Unsigned { bit_size } => {
let max = 2u128.pow(bit_size) - 1;
let max = if bit_size == 128 { u128::MAX } else { 2u128.pow(bit_size) - 1 };
if negative {
return Some(format!("0..={}", max));
}
Expand Down
Loading

0 comments on commit 8783e48

Please sign in to comment.