Skip to content
This repository has been archived by the owner on Apr 9, 2024. It is now read-only.

feat!: add block opcode #114

Merged
merged 19 commits into from
Feb 28, 2023
Merged
48 changes: 45 additions & 3 deletions acir/src/circuit/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,25 @@ use crate::serialization::{read_n, read_u16, read_u32, write_bytes, write_u16, w
use crate::BlackBoxFunc;
use serde::{Deserialize, Serialize};

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash, Copy, Default)]
pub struct BlockId(u32);

/// Operation on a block
guipublic marked this conversation as resolved.
Show resolved Hide resolved
/// We can either write or read at a block index
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MemOp {
pub operation: Expression,
guipublic marked this conversation as resolved.
Show resolved Hide resolved
pub index: Expression,
pub value: Expression,
}

#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Opcode {
Arithmetic(Expression),
BlackBoxFuncCall(BlackBoxFuncCall),
Directive(Directive),
// Abstract read/write operations on a block of data
Block(BlockId, Vec<MemOp>),
}

impl Opcode {
Expand All @@ -21,17 +35,18 @@ impl Opcode {
Opcode::Arithmetic(_) => "arithmetic",
Opcode::Directive(directive) => directive.name(),
Opcode::BlackBoxFuncCall(g) => g.name.name(),
Opcode::Block(_, _) => "block",
}
}
// We have three types of opcodes allowed in the IR
// Expression, BlackBoxFuncCall and Directives
// When we serialize these opcodes, we use the index

// When we serialize the opcodes, we use the index
// to uniquely identify which category of opcode we are dealing with.
pub(crate) fn to_index(&self) -> u8 {
match self {
Opcode::Arithmetic(_) => 0,
Opcode::BlackBoxFuncCall(_) => 1,
Opcode::Directive(_) => 2,
Opcode::Block(_, _) => 3,
}
}

Expand All @@ -53,6 +68,17 @@ impl Opcode {
Opcode::Arithmetic(expr) => expr.write(writer),
Opcode::BlackBoxFuncCall(func_call) => func_call.write(writer),
Opcode::Directive(directive) => directive.write(writer),
Opcode::Block(id, trace) => {
write_u32(&mut writer, id.0)?;
write_u32(&mut writer, trace.len() as u32)?;

for op in trace {
op.operation.write(&mut writer)?;
op.index.write(&mut writer)?;
op.value.write(&mut writer)?;
}
Ok(())
}
}
}
pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
Expand All @@ -74,6 +100,18 @@ impl Opcode {
let directive = Directive::read(reader)?;
Ok(Opcode::Directive(directive))
}
3 => {
let id = read_u32(&mut reader)?;
let len = read_u32(&mut reader)?;
let mut trace = Vec::with_capacity(len as usize);
for _i in 0..len {
let operation = Expression::read(&mut reader)?;
let index = Expression::read(&mut reader)?;
let value = Expression::read(&mut reader)?;
trace.push(MemOp { operation, index, value });
}
Ok(Opcode::Block(BlockId(id), trace))
}
_ => Err(std::io::ErrorKind::InvalidData.into()),
}
}
Expand Down Expand Up @@ -175,6 +213,10 @@ impl std::fmt::Display for Opcode {
witnesses.last().unwrap().witness_index()
),
},
Opcode::Block(id, trace) => {
write!(f, "BLOCK ")?;
write!(f, "(id: {}, len: {}) ", id.0, trace.len())
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion acir/src/native_types/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ impl Expression {
self.mul_terms.is_empty() && self.linear_combinations.is_empty()
}

/// Returns `true` if highest degree term in the expression is one.
/// Returns `true` if highest degree term in the expression is one or less.
///
/// - `mul_term` in an expression contains degree-2 terms
/// - `linear_combinations` contains degree-1 terms
Expand Down
5 changes: 2 additions & 3 deletions acvm/src/compiler/transformers/fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ impl FallbackTransformer {

for opcode in acir.opcodes {
let bb_func_call = match &opcode {
Opcode::Arithmetic(_) | Opcode::Directive(_) => {
// If it is not a black box function, then it is a directive or
// an arithmetic expression which are always supported
Opcode::Arithmetic(_) | Opcode::Directive(_) | Opcode::Block(_, _) => {
guipublic marked this conversation as resolved.
Show resolved Hide resolved
// directive, arithmetic expression or block are handled by acvm
acir_supported_opcodes.push(opcode);
continue;
}
Expand Down
12 changes: 7 additions & 5 deletions acvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use acir::{
native_types::{Expression, Witness},
BlackBoxFunc,
};
use pwg::block::Blocks;
use std::collections::BTreeMap;
use thiserror::Error;

Expand Down Expand Up @@ -61,13 +62,14 @@ pub trait PartialWitnessGenerator {
fn solve(
&self,
initial_witness: &mut BTreeMap<Witness, FieldElement>,
mut opcodes: Vec<Opcode>,
mut opcode_to_solve: Vec<Opcode>,
) -> Result<(), OpcodeResolutionError> {
let mut unresolved_opcodes: Vec<Opcode> = Vec::new();
while !opcodes.is_empty() {
let mut blocks = Blocks::default();
while !opcode_to_solve.is_empty() {
unresolved_opcodes.clear();

for opcode in &opcodes {
for opcode in &opcode_to_solve {
let resolution = match opcode {
Opcode::Arithmetic(expr) => ArithmeticSolver::solve(initial_witness, expr),
Opcode::BlackBoxFuncCall(bb_func) => {
Expand All @@ -76,8 +78,8 @@ pub trait PartialWitnessGenerator {
Opcode::Directive(directive) => {
Self::solve_directives(initial_witness, directive)
}
Opcode::Block(id, trace) => blocks.solve(*id, trace, initial_witness),
};

match resolution {
Ok(_) => {
// We do nothing in the happy case
Expand All @@ -91,7 +93,7 @@ pub trait PartialWitnessGenerator {
Err(err) => return Err(err),
}
}
std::mem::swap(&mut opcodes, &mut unresolved_opcodes);
std::mem::swap(&mut opcode_to_solve, &mut unresolved_opcodes);
}
Ok(())
}
Expand Down
36 changes: 12 additions & 24 deletions acvm/src/pwg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ use acir::{
};
use std::collections::BTreeMap;

use self::arithmetic::ArithmeticSolver;

// arithmetic
pub mod arithmetic;
// Directives
pub mod directives;
// black box functions
pub mod block;
pub mod hash;
pub mod logic;
pub mod range;
Expand All @@ -30,37 +33,22 @@ pub fn witness_to_value(
None => Err(OpcodeNotSolvable::MissingAssignment(witness.0).into()),
}
}

// TODO: There is an issue open to decide on whether we need to get values from Expressions
// TODO versus just getting values from Witness
pub fn get_value(
expr: &Expression,
initial_witness: &BTreeMap<Witness, FieldElement>,
) -> Result<FieldElement, OpcodeResolutionError> {
let mut result = expr.q_c;

for term in &expr.linear_combinations {
let coefficient = term.0;
let variable = term.1;

// Get the value assigned to that variable
let assignment = *witness_to_value(initial_witness, variable)?;

result += coefficient * assignment;
}

for term in &expr.mul_terms {
let coefficient = term.0;
let lhs_variable = term.1;
let rhs_variable = term.2;

// Get the values assigned to those variables
let lhs_assignment = *witness_to_value(initial_witness, lhs_variable)?;
let rhs_assignment = *witness_to_value(initial_witness, rhs_variable)?;

result += coefficient * lhs_assignment * rhs_assignment;
let expr = ArithmeticSolver::evaluate(expr, initial_witness);
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
match expr.to_const() {
Some(value) => Ok(value),
None => {
Err(OpcodeResolutionError::OpcodeNotSolvable(OpcodeNotSolvable::MissingAssignment(
ArithmeticSolver::any_witness_from_expression(&expr).unwrap().0,
)))
}
}

Ok(result)
}

// Inserts `value` into the initial witness map
Expand Down
19 changes: 17 additions & 2 deletions acvm/src/pwg/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{OpcodeNotSolvable, OpcodeResolutionError};
pub struct ArithmeticSolver;

#[allow(clippy::enum_variant_names)]
enum GateStatus {
pub enum GateStatus {
GateSatisfied(FieldElement),
GateSolvable(FieldElement, (FieldElement, Witness)),
GateUnsolvable,
Expand Down Expand Up @@ -162,7 +162,7 @@ impl ArithmeticSolver {
/// Returns the summation of all of the variables, plus the unknown variable
/// Returns None, if there is more than one unknown variable
/// We cannot assign
fn solve_fan_in_term(
pub fn solve_fan_in_term(
arith_gate: &Expression,
witness_assignments: &BTreeMap<Witness, FieldElement>,
) -> GateStatus {
Expand Down Expand Up @@ -229,6 +229,21 @@ impl ArithmeticSolver {
result.q_c += expr.q_c;
result
}

// Returns one witness belonging to an expression, in no relevant order
// Returns None if the expression is const
// The function is used during partial witness generation to report unsolved witness
pub fn any_witness_from_expression(expr: &Expression) -> Option<Witness> {
if expr.linear_combinations.is_empty() {
if expr.mul_terms.is_empty() {
None
} else {
Some(expr.mul_terms[0].1)
}
} else {
Some(expr.linear_combinations[0].1)
}
}
}

#[test]
Expand Down
Loading