-
Notifications
You must be signed in to change notification settings - Fork 206
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(ssa): refactor ACIR generation code (#750)
- Loading branch information
1 parent
f329379
commit cfb13c2
Showing
18 changed files
with
2,140 additions
and
1,407 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
221 changes: 221 additions & 0 deletions
221
crates/noirc_evaluator/src/ssa/acir_gen/internal_var.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
85
crates/noirc_evaluator/src/ssa/acir_gen/internal_var_cache.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.