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

chore(ssa refactor): Add code to handle less than comparison #1433

Merged
merged 22 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
83de289
add field mul and div
kevaundray May 27, 2023
1ef2ca9
add code to process field mul and div
kevaundray May 27, 2023
0637626
add assert example
kevaundray May 27, 2023
18c4274
add `is_equal` constraint
kevaundray May 27, 2023
171d5d9
add `eq_var` method for AcirVar
kevaundray May 27, 2023
f1bfdc6
process `Constrain` instruction and BinaryOp::Eq
kevaundray May 27, 2023
58edbc9
add TODO for more than the maximum number of bits
kevaundray May 28, 2023
3c8c901
add numeric_cast_var method which constrains a variable to be equal t…
kevaundray May 28, 2023
a58f7a4
implement casting for numeric types
kevaundray May 28, 2023
ca60032
add simple range constraint example
kevaundray May 28, 2023
fbdfefa
add constraints for `more_than_eq`
kevaundray May 28, 2023
327c2f6
- add more_than_eq method
kevaundray May 28, 2023
90c2102
add method to process less than binary operation
kevaundray May 28, 2023
99f414b
add example
kevaundray May 28, 2023
8d7f7f9
assign result of cast operation
kevaundray May 29, 2023
a7f1178
Merge branch 'kw/range-constraints' into kw/add-comparison
kevaundray May 29, 2023
6b159ee
add `y` as an input value
kevaundray May 29, 2023
1c2cee5
return optimized circuit
kevaundray May 29, 2023
b935436
Merge branch 'kw/add-optimized-circuit' into kw/add-comparison
kevaundray May 29, 2023
7d8753b
Addressed in Address GtEq extra opcodes #1444
kevaundray May 30, 2023
7241896
Merge remote-tracking branch 'origin/master' into kw/add-comparison
kevaundray May 30, 2023
c654b5d
Merge remote-tracking branch 'origin/master' into kw/add-comparison
kevaundray May 30, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
authors = [""]
compiler_version = "0.1"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
x = "3"
y = "4"
z = "5"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Tests a very simple program.
//
// The features being tested are:
// Binary addition, multiplication, division
// x = 3, y = 4, z = 5
fn main(x : Field, y : Field, z : Field) -> pub Field {
let a = x + x; // 3 + 3 = 6
let b = a - y; // 6 - 4 = 2
let c = b * z; // 2 * 5 = 10
let d = c / a; // 10 / 6 (This uses field inversion, so we test it by multiplying by `a`)
d * a
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
authors = [""]
compiler_version = "0.1"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x = "3"
y = "3"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Tests a very simple program.
//
// The features being tested is assertion
fn main(x : Field, y : Field) {
assert(x == y);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
authors = [""]
compiler_version = "0.1"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x = "3"
y = "4"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Tests a very simple program.
//
// The features being tested is comparison
fn main(x : Field, y : Field) {
assert(x as u32 < y as u32);
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
authors = [""]
compiler_version = "0.1"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x = "3"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Tests a very simple program.
//
// The features being tested is casting to an integer
fn main(x : Field) {
let _z = x as u32;
}
27 changes: 23 additions & 4 deletions crates/noirc_evaluator/src/ssa_refactor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ pub(crate) fn optimize_into_acir(program: Program) -> GeneratedAcir {
/// to use the new ssa module to process Noir code.
pub fn experimental_create_circuit(
program: Program,
_np_language: Language,
_is_opcode_supported: &impl Fn(&AcirOpcode) -> bool,
np_language: Language,
is_opcode_supported: &impl Fn(&AcirOpcode) -> bool,
_enable_logging: bool,
_show_output: bool,
) -> Result<(Circuit, Abi), RuntimeError> {
Expand All @@ -65,8 +65,27 @@ pub fn experimental_create_circuit(
let public_parameters =
PublicInputs(public_abi.param_witnesses.values().flatten().copied().collect());
let return_values = PublicInputs(return_witnesses.into_iter().collect());
let circuit = Circuit { current_witness_index, opcodes, public_parameters, return_values };
Ok((circuit, abi))

// This region of code will optimize the ACIR bytecode for a particular backend
// it will be removed in the near future and we will subsequently only return the
// unoptimized backend-agnostic bytecode here
let optimized_circuit = {
use crate::errors::RuntimeErrorKind;
use acvm::compiler::optimizers::simplify::CircuitSimplifier;

let abi_len = abi.field_count();

let simplifier = CircuitSimplifier::new(abi_len);
acvm::compiler::compile(
Circuit { current_witness_index, opcodes, public_parameters, return_values },
np_language,
is_opcode_supported,
&simplifier,
)
.map_err(|_| RuntimeErrorKind::Spanless(String::from("produced an acvm compile error")))?
};

Ok((optimized_circuit, abi))
}

impl Ssa {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use super::generated_acir::GeneratedAcir;
use crate::ssa_refactor::ir::types::NumericType;

use super::{errors::AcirGenError, generated_acir::GeneratedAcir};
use acvm::{
acir::native_types::{Expression, Witness},
FieldElement,
};
use std::{collections::HashMap, hash::Hash};
use std::{borrow::Cow, collections::HashMap, hash::Hash};

#[derive(Debug, Default)]
/// Context object which holds the relationship between
Expand All @@ -29,6 +31,9 @@ pub(crate) struct AcirContext {
/// then the `acir_ir` will be populated to assert this
/// addition.
acir_ir: GeneratedAcir,

/// Maps an `AcirVar` to its known bit size.
variables_to_bit_sizes: HashMap<AcirVar, u32>,
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
}

impl AcirContext {
Expand Down Expand Up @@ -100,6 +105,18 @@ impl AcirContext {
self.assert_eq_var(var, one_var);
}

/// Returns an `AcirVar` that is `1` if `lhs` equals `rhs` and
/// 0 otherwise.
pub(crate) fn eq_var(&mut self, lhs: AcirVar, rhs: AcirVar) -> AcirVar {
let lhs_data = &self.data[&lhs];
let rhs_data = &self.data[&rhs];

let lhs_expr = lhs_data.to_expression();
let rhs_expr = rhs_data.to_expression();

let is_equal_witness = self.acir_ir.is_equal(&lhs_expr, &rhs_expr);
self.add_data(AcirVarData::Witness(is_equal_witness))
}
/// Constrains the `lhs` and `rhs` to be equal.
pub(crate) fn assert_eq_var(&mut self, lhs: AcirVar, rhs: AcirVar) {
// TODO: could use sub_var and then assert_eq_zero
Expand Down Expand Up @@ -151,7 +168,7 @@ impl AcirContext {
match (lhs_data, rhs_data) {
(AcirVarData::Witness(witness), AcirVarData::Expr(expr))
| (AcirVarData::Expr(expr), AcirVarData::Witness(witness)) => {
let expr_as_witness = self.acir_ir.expression_to_witness(expr);
let expr_as_witness = self.acir_ir.get_or_create_witness(expr);
let mut expr = Expression::default();
expr.push_multiplication_term(FieldElement::one(), *witness, expr_as_witness);

Expand All @@ -176,8 +193,8 @@ impl AcirContext {
self.add_data(AcirVarData::Const(*lhs_constant * *rhs_constant))
}
(AcirVarData::Expr(lhs_expr), AcirVarData::Expr(rhs_expr)) => {
let lhs_expr_as_witness = self.acir_ir.expression_to_witness(lhs_expr);
let rhs_expr_as_witness = self.acir_ir.expression_to_witness(rhs_expr);
let lhs_expr_as_witness = self.acir_ir.get_or_create_witness(lhs_expr);
let rhs_expr_as_witness = self.acir_ir.get_or_create_witness(rhs_expr);
let mut expr = Expression::default();
expr.push_multiplication_term(
FieldElement::one(),
Expand Down Expand Up @@ -234,14 +251,86 @@ impl AcirContext {
// TODO: Add caching to prevent expressions from being needlessly duplicated
let witness = match acir_var_data {
AcirVarData::Const(constant) => {
self.acir_ir.expression_to_witness(&Expression::from(*constant))
self.acir_ir.get_or_create_witness(&Expression::from(*constant))
}
AcirVarData::Expr(expr) => self.acir_ir.expression_to_witness(expr),
AcirVarData::Expr(expr) => self.acir_ir.get_or_create_witness(expr),
AcirVarData::Witness(witness) => *witness,
};
self.acir_ir.push_return_witness(witness);
}

/// Constrains the `AcirVar` variable to be of type `NumericType`.
pub(crate) fn numeric_cast_var(
&mut self,
variable: AcirVar,
numeric_type: &NumericType,
) -> Result<AcirVar, AcirGenError> {
let data = &self.data[&variable];
match numeric_type {
NumericType::Signed { .. } => todo!("signed integer conversion is unimplemented"),
NumericType::Unsigned { bit_size } => {
let data_expr = data.to_expression();
let witness = self.acir_ir.get_or_create_witness(&data_expr);
self.acir_ir.range_constraint(witness, *bit_size)?;
// Log the bit size for this variable
joss-aztec marked this conversation as resolved.
Show resolved Hide resolved
self.variables_to_bit_sizes.insert(variable, *bit_size);
}
NumericType::NativeField => {
// If someone has made a cast to a `Field` type then this is a Noop.
//
// The reason for doing this in code is for type safety; ie you have an
// integer, but a function requires the parameter to be a Field.
}
}
Ok(variable)
}

/// Returns an `AcirVar` which will be `1` if lhs >= rhs
/// and `0` otherwise.
fn more_than_eq_var(&mut self, lhs: AcirVar, rhs: AcirVar) -> Result<AcirVar, AcirGenError> {
let lhs_data = &self.data[&lhs];
let rhs_data = &self.data[&rhs];

let lhs_expr = lhs_data.to_expression();
let rhs_expr = rhs_data.to_expression();

let lhs_bit_size = self.variables_to_bit_sizes.get(&lhs).expect("comparisons cannot be made on variables with no known max bit size. This should have been caught by the frontend");
let rhs_bit_size = self.variables_to_bit_sizes.get(&rhs).expect("comparisons cannot be made on variables with no known max bit size. This should have been caught by the frontend");

// This is a conservative choice. Technically, we should just be able to take
// the bit size of the `lhs` (upper bound), but we need to check/document what happens
// if the bit_size is not enough to represent both witnesses.
// An example is the following: (a as u8) >= (b as u32)
// If the equality is true, then it means that `b` also fits inside
// of a u8.
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
// But its not clear what happens if the equality is false,
// and we 8 bits to `more_than_eq_comparison`. The conservative
// choice chosen is to use 32.
let bit_size = *std::cmp::max(lhs_bit_size, rhs_bit_size);

let is_greater_than_eq =
self.acir_ir.more_than_eq_comparison(&lhs_expr, &rhs_expr, bit_size)?;

Ok(self.add_data(AcirVarData::Witness(is_greater_than_eq)))
}

/// Returns an `AcirVar` which will be `1` if lhs < rhs
/// and `0` otherwise.
pub(crate) fn less_than_var(
&mut self,
lhs: AcirVar,
rhs: AcirVar,
) -> Result<AcirVar, AcirGenError> {
// Flip the result of calling more than equal method to
// compute less than.
let comparison = self.more_than_eq_var(lhs, rhs)?;

let one = self.add_constant(FieldElement::one());
let comparison_negated = self.sub_var(one, comparison);
jfecher marked this conversation as resolved.
Show resolved Hide resolved

Ok(comparison_negated)
}

/// Terminates the context and takes the resulting `GeneratedAcir`
pub(crate) fn finish(self) -> GeneratedAcir {
self.acir_ir
Expand Down Expand Up @@ -300,6 +389,15 @@ impl AcirVarData {
}
None
}
/// Converts all enum variants to an Expression.
/// TODO: can refactor code in this file to use this
pub(crate) fn to_expression(&self) -> Cow<Expression> {
match self {
AcirVarData::Witness(witness) => Cow::Owned(Expression::from(*witness)),
AcirVarData::Expr(expr) => Cow::Borrowed(expr),
AcirVarData::Const(constant) => Cow::Owned(Expression::from(*constant)),
}
}
}

/// A Reference to an `AcirVarData`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ impl AcirGenError {
pub(crate) fn message(&self) -> String {
match self {
AcirGenError::InvalidRangeConstraint { num_bits } => {
// Don't apply any constraints if the range is for the maximum number of bits
// Don't apply any constraints if the range is for the maximum number of bits or more.
format!(
"All Witnesses are by default u{num_bits}. Applying this type does not apply any constraints.")
"All Witnesses are by default u{num_bits} Applying this type does not apply any constraints.\n We also currently do not allow integers of size more than {num_bits}, this will be handled by BigIntegers.")
}
AcirGenError::IndexOutOfBounds { index, array_size } => {
format!("Index out of bounds, array has size {array_size}, but index was {index}")
Expand Down
Loading