Skip to content

Commit

Permalink
chore(ssa): refactor ACIR generation code (#750)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevaundray authored Feb 8, 2023
1 parent f329379 commit cfb13c2
Show file tree
Hide file tree
Showing 18 changed files with 2,140 additions and 1,407 deletions.
1,550 changes: 144 additions & 1,406 deletions crates/noirc_evaluator/src/ssa/acir_gen.rs

Large diffs are not rendered by default.

647 changes: 647 additions & 0 deletions crates/noirc_evaluator/src/ssa/acir_gen/constraints.rs

Large diffs are not rendered by default.

221 changes: 221 additions & 0 deletions crates/noirc_evaluator/src/ssa/acir_gen/internal_var.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
use crate::{
ssa::acir_gen::{const_from_expression, expression_to_witness, optional_expression_to_witness},
ssa::node::NodeId,
Evaluator,
};
use acvm::{
acir::native_types::{Expression, Witness},
FieldElement,
};

#[derive(Clone, Debug, Eq)]
pub struct InternalVar {
// A multi-variate degree-2 polynomial
expression: Expression,
// A witness associated to `expression`.
// semantically `cached_witness` and `expression`
// should be the equal.
//
// Example: `z = x + y`
// `z` can be seen as the same to `x + y`
// ie if `z = 10` , then `x+y` must be equal to
// 10.
// There are places where we can use `cached_witness`
// in place of `expression` for performance reasons
// due to the fact that `cached_witness` is a single variable
// whereas `expression` is a multi-variate polynomial which can
// contain many degree-2 terms.
//
// Note that we also protect against the case that `expression`
// changes and `cached_witness` becomes "dirty" or outdated.
// This is because one is not allowed to modify the `expression`
// once set. Additionally, there are no methods that allow one
// to modify the `cached_witness`
cached_witness: Option<Witness>,
id: Option<NodeId>,
}

impl InternalVar {
pub(crate) fn expression(&self) -> &Expression {
&self.expression
}
pub(crate) fn set_id(&mut self, id: NodeId) {
self.id = Some(id)
}
pub(crate) fn cached_witness(&self) -> &Option<Witness> {
&self.cached_witness
}

/// If the InternalVar holds a constant expression
/// Return that constant.Otherwise, return None.
pub(super) fn to_const(&self) -> Option<FieldElement> {
const_from_expression(&self.expression)
}

/// The expression term is degree-2 multi-variate polynomial, so
/// in order to check if if represents a constant,
/// we need to check that the degree-2 terms `mul_terms`
/// and the degree-1 terms `linear_combinations` do not exist.
///
/// Example: f(x,y) = xy + 5
/// `f` is not a constant expression because there
/// is a bi-variate term `xy`.
/// Example: f(x,y) = x + y + 5
/// `f` is not a constant expression because there are
/// two uni-variate terms `x` and `y`
/// Example: f(x,y) = 10
/// `f` is a constant expression because there are no
/// bi-variate or uni-variate terms, just a constant.
fn is_const_expression(&self) -> bool {
self.expression.is_const()
}

/// Creates an `InternalVar` from an `Expression`.
/// If `Expression` represents a degree-1 polynomial
/// then we also assign it to the `cached_witness`
pub(crate) fn from_expression(expression: Expression) -> InternalVar {
let witness = optional_expression_to_witness(&expression);
InternalVar { expression, cached_witness: witness, id: None }
}

pub(crate) fn zero_expr() -> InternalVar {
InternalVar::from_expression(Expression::zero())
}

/// Creates an `InternalVar` from a `Witness`.
/// Since a `Witness` can alway be coerced into an
/// Expression, this method is infallible.
pub(crate) fn from_witness(witness: Witness) -> InternalVar {
InternalVar {
expression: Expression::from(&witness),
cached_witness: Some(witness),
id: None,
}
}

/// Creates an `InternalVar` from a `FieldElement`.
pub(crate) fn from_constant(constant: FieldElement) -> InternalVar {
InternalVar { expression: Expression::from_field(constant), cached_witness: None, id: None }
}

/// Generates a `Witness` that is equal to the `expression`.
/// - If a `Witness` has previously been generated
/// we return that.
/// - If the Expression represents a constant, we return None.
pub(crate) fn get_or_compute_witness(
&mut self,
evaluator: &mut Evaluator,
create_witness_for_const: bool,
) -> Option<Witness> {
// Check if we've already generated a `Witness` which is equal to
// the stored `Expression`
if let Some(witness) = self.cached_witness {
return Some(witness);
}

// There are cases where we need to convert a constant expression
// into a witness.
if !create_witness_for_const && self.is_const_expression() {
return None;
}

self.cached_witness = Some(expression_to_witness(self.expression.clone(), evaluator));

self.cached_witness
}
}

impl PartialEq for InternalVar {
fn eq(&self, other: &Self) -> bool {
// An InternalVar is Equal to another InternalVar if _any_ of the fields
// in the InternalVar are equal.

let expressions_are_same = self.expression == other.expression;

// Check if `cached_witnesses` are the same
//
// This may happen if the expressions are the same
// but one is simplified and the other is not.
//
// The caller whom created both `InternalVar` objects
// may have known this and set their cached_witnesses to
// be the same.
// Example:
// z = 2*x + y
// t = x + x + y
// After simplification, it is clear that both RHS are the same
// However, since when we check for equality, we do not check for
// simplification, or in particular, we may eagerly assume
// the two expressions of the RHS are different.
//
// The caller can notice this and thus do:
// InternalVar::new(expr: 2*x + y, witness: z)
// InternalVar::new(expr: x + x + y, witness: z)
let cached_witnesses_same =
self.cached_witness.is_some() && self.cached_witness == other.cached_witness;

let node_ids_same = self.id.is_some() && self.id == other.id;

expressions_are_same || cached_witnesses_same || node_ids_same
}
}

impl From<Expression> for InternalVar {
fn from(expression: Expression) -> InternalVar {
InternalVar::from_expression(expression)
}
}

impl From<Witness> for InternalVar {
fn from(witness: Witness) -> InternalVar {
InternalVar::from_witness(witness)
}
}

impl From<FieldElement> for InternalVar {
fn from(constant: FieldElement) -> InternalVar {
InternalVar::from_constant(constant)
}
}

#[cfg(test)]
mod tests {
use crate::{ssa::acir_gen::InternalVar, Evaluator};
use acvm::{acir::native_types::Witness, FieldElement};

#[test]
fn internal_var_const_expression() {
let mut evaluator = Evaluator::new();

let expected_constant = FieldElement::from(123456789u128);

// Initialize an InternalVar with a FieldElement
let mut internal_var = InternalVar::from_constant(expected_constant);

// We currently do not create witness when the InternalVar was created using a constant
let witness = internal_var.get_or_compute_witness(&mut evaluator, false);
assert!(witness.is_none());

match internal_var.to_const() {
Some(got_constant) => assert_eq!(got_constant, expected_constant),
None => {
panic!("`InternalVar` was initialized with a constant, so a field element was expected")
}
}
}
#[test]
fn internal_var_from_witness() {
let mut evaluator = Evaluator::new();

let expected_witness = Witness(1234);
// Initialize an InternalVar with a `Witness`
let mut internal_var = InternalVar::from_witness(expected_witness);

// We should get back the same `Witness`
let got_witness = internal_var.get_or_compute_witness(&mut evaluator, false);
match got_witness {
Some(got_witness) => assert_eq!(got_witness, expected_witness),
None => panic!("expected a `Witness` value"),
}
}
}
85 changes: 85 additions & 0 deletions crates/noirc_evaluator/src/ssa/acir_gen/internal_var_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use crate::{
ssa::{
acir_gen::InternalVar,
context::SsaContext,
node::{Node, NodeId, NodeObject, ObjectType},
},
Evaluator,
};
use acvm::FieldElement;
use std::collections::HashMap;

#[derive(Default)]
pub struct InternalVarCache {
inner: HashMap<NodeId, InternalVar>,
}

impl InternalVarCache {
//This function stores the substitution with the arithmetic expression in the cache
//When an instruction performs arithmetic operation, its output can be represented as an arithmetic expression of its arguments
//Substitute a node object as an arithmetic expression
// Returns `None` if `NodeId` represents an array pointer.
pub(super) fn get_or_compute_internal_var(
&mut self,
id: NodeId,
evaluator: &mut Evaluator,
ctx: &SsaContext,
) -> Option<InternalVar> {
if let Some(internal_var) = self.inner.get(&id) {
return Some(internal_var.clone());
}

let mut var = match ctx.try_get_node(id)? {
NodeObject::Const(c) => {
let field_value = FieldElement::from_be_bytes_reduce(&c.value.to_bytes_be());
InternalVar::from_constant(field_value)
}
NodeObject::Obj(variable) => {
let variable_type = variable.get_type();
match variable_type {
ObjectType::Boolean
| ObjectType::NativeField
| ObjectType::Signed(..)
| ObjectType::Unsigned(..) => {
let witness =
variable.witness.unwrap_or_else(|| evaluator.add_witness_to_cs());
InternalVar::from_witness(witness)
}
ObjectType::Pointer(_) | ObjectType::NotAnObject => return None,
ObjectType::Function => {
unreachable!("ICE: functions should have been removed by this stage")
}
}
}
NodeObject::Function(..) => {
unreachable!("ICE: functions should have been removed by this stage")
}
// TODO: Why do we create a `Witness` for an instruction (Guillaume)
NodeObject::Instr(..) => {
let witness = evaluator.add_witness_to_cs();
InternalVar::from_witness(witness)
}
};

var.set_id(id);
self.inner.insert(id, var.clone());
Some(var)
}

pub(super) fn get_or_compute_internal_var_unwrap(
&mut self,
id: NodeId,
evaluator: &mut Evaluator,
ctx: &SsaContext,
) -> InternalVar {
self.get_or_compute_internal_var(id, evaluator, ctx)
.expect("ICE: `NodeId` type cannot be converted into an `InternalVar`")
}

pub(super) fn update(&mut self, id: NodeId, var: InternalVar) {
self.inner.insert(id, var);
}
pub(super) fn get(&mut self, id: &NodeId) -> Option<&InternalVar> {
self.inner.get(id)
}
}
85 changes: 85 additions & 0 deletions crates/noirc_evaluator/src/ssa/acir_gen/memory_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use crate::ssa::{
acir_gen::InternalVar,
context::SsaContext,
mem::{ArrayId, MemArray},
};
use acvm::acir::native_types::Witness;
use iter_extended::vecmap;
use std::collections::HashMap;

// maps memory address to expression
#[derive(Default)]
pub struct MemoryMap {
inner: HashMap<u32, InternalVar>,
}

impl MemoryMap {
//Map the outputs into the array
pub(crate) fn map_array(&mut self, a: ArrayId, outputs: &[Witness], ctx: &SsaContext) {
let array = &ctx.mem[a];
let address = array.adr;
for i in 0..array.len {
let var = if i < outputs.len() as u32 {
InternalVar::from(outputs[i as usize])
} else {
InternalVar::zero_expr()
};
self.inner.insert(address + i, var);
}
}

// Load array values into InternalVars
// If create_witness is true, we create witnesses for values that do not have witness
pub(crate) fn load_array(&mut self, array: &MemArray) -> Vec<InternalVar> {
vecmap(0..array.len, |offset| {
self.load_array_element_constant_index(array, offset)
.expect("infallible: array out of bounds error")
})
}

// Loads the associated `InternalVar` for the element
// in the `array` at the given `offset`.
//
// First we check if the address of the array element
// is in the memory_map. If not, then we check the `array`
//
// We do not check the `MemArray` initially because the
// `MemoryMap` holds the most updated InternalVar
// associated to the array element.
// TODO: specify what could change between the two?
//
// Returns `None` if `offset` is out of bounds.
pub(crate) fn load_array_element_constant_index(
&mut self,
array: &MemArray,
offset: u32,
) -> Option<InternalVar> {
// Check to see if the index has gone out of bounds
let array_length = array.len;
if offset >= array_length {
return None; // IndexOutOfBoundsError
}

let address_of_element = array.absolute_adr(offset);

// Check the memory_map to see if the element is there
if let Some(internal_var) = self.inner.get(&address_of_element) {
return Some(internal_var.clone());
};

let array_element = array.values[offset as usize].clone();

// Compiler sanity check
//
// Since the only time we take the array values
// from the array is when it has been defined in the
// ABI. We know that it must have been initialized with a `Witness`
array_element.cached_witness().expect("ICE: since the value is not in the memory_map it must have came from the ABI, so it is a Witness");

Some(array_element)
}

pub(crate) fn insert(&mut self, key: u32, value: InternalVar) -> Option<InternalVar> {
self.inner.insert(key, value)
}
}
Loading

0 comments on commit cfb13c2

Please sign in to comment.