diff --git a/bberg/Cargo.toml b/bberg/Cargo.toml index a5ed90bc53..f732b0e974 100644 --- a/bberg/Cargo.toml +++ b/bberg/Cargo.toml @@ -17,9 +17,6 @@ log = "0.4.17" rand = "0.8.5" ast = { version = "0.1.0", path = "../ast" } - -# TODO: we probably want to pull in the local version of nargo while i am working on it here - # Include acvm brillig module such that we can serialise and deserialise them acvm = { git = "https://github.com/noir-lang/noir", directory = "acvm-repo/acvm" } base64 = "*" diff --git a/bberg/brillig.asm b/bberg/brillig.asm deleted file mode 100644 index 317f34d740..0000000000 --- a/bberg/brillig.asm +++ /dev/null @@ -1,122 +0,0 @@ - - degree 256; - - reg pc[@pc]; - reg X[<=]; - reg Y[<=]; - reg Z[<=]; - - reg jump_ptr; // Store the location of a call - reg addr; // used for memory operations - reg tmp; // used for temporary storage - reg r0; // 12 registers for now - reg r1; - reg r2; - reg r3; - reg r4; - reg r5; - reg r6; - reg r7; - reg r8; - reg r9; - reg r10; - reg r11; - - // ============== iszero check for X ======================= - col witness XInv; - col witness XIsZero; - XIsZero = 1 - X * XInv; - XIsZero * X = 0; - XIsZero * (1 - XIsZero) = 0; - - - // =============== read-write memory ======================= - // Read-write memory. Columns are sorted by m_addr and - // then by m_step. m_change is 1 if and only if m_addr changes - // in the next row. - col witness m_addr; - col witness m_step; - col witness m_change; - col witness m_value; - // If we have an operation at all (needed because this needs to be a permutation) - col witness m_op; - // If the operation is a write operation. - col witness m_is_write; - col witness m_is_read; - - // positive numbers (assumed to be much smaller than the field order) - col fixed POSITIVE(i) { i + 1 }; - col fixed FIRST = [1] + [0]*; - col fixed LAST(i) { FIRST(i + 1) }; - col fixed STEP(i) { i }; - - m_change * (1 - m_change) = 0; - - // if m_change is zero, m_addr has to stay the same. - (m_addr' - m_addr) * (1 - m_change) = 0; - - // Except for the last row, if m_change is 1, then m_addr has to increase, - // if it is zero, m_step has to increase. - (1 - LAST) { m_change * (m_addr' - m_addr) + (1 - m_change) * (m_step' - m_step) } in POSITIVE; - - m_op * (1 - m_op) = 0; - m_is_write * (1 - m_is_write) = 0; - m_is_read * (1 - m_is_read) = 0; - // m_is_write can only be 1 if m_op is 1. - m_is_write * (1 - m_op) = 0; - m_is_read * (1 - m_op) = 0; - m_is_read * m_is_write = 0; - - - - - - // If the next line is a read and we stay at the same address, then the - // value cannot change. - (1 - m_is_write') * (1 - m_change) * (m_value' - m_value) = 0; - - // If the next line is a read and we have an address change, - // then the value is zero. - (1 - m_is_write') * m_change * m_value' = 0; - - - // ============== memory instructions ============== - instr store X { { addr, STEP, X } is m_is_write { m_addr, m_step, m_value } } - instr load -> X { { addr, STEP, X } is m_is_read { m_addr, m_step, m_value } } - - - - /// Add - /// Take in two input registers, send the result into the output register - instr add Y, Z -> X { - X = Y + Z - } - - /// Sub - instr sub Y, Z -> X { - X = Y - Z - } - - /// Is the value equal to 0 - uses only assignment registers - instr eq X -> Y { Y = XIsZero } - - /// Mul - instr mul Y, Z -> X { - X = Y * Z - } - - /// move - // TODO: move should zero out the sending register? - instr mov Y -> X { - X = Y - } - - // When we get a call, we want - instr call l: label { pc' = l, jump_ptr' = pc + 1 } - instr ret { pc' = jump_ptr } - - /// Jumps - instr jump l: label { pc' = l } - instr jumpi X, l: label { pc' = (1 - XIsZero) * l + XIsZero * (pc + 1) } - instr jumpni X, l: label { pc' = XIsZero * l + (1 - XIsZero) * (pc + 1) } - diff --git a/bberg/brillig_out.asm b/bberg/brillig_out.asm deleted file mode 100644 index 3af9de9ff1..0000000000 --- a/bberg/brillig_out.asm +++ /dev/null @@ -1,137 +0,0 @@ - -machine Main { - - - degree 256; - - reg pc[@pc]; - reg X[<=]; - reg Y[<=]; - reg Z[<=]; - - reg jump_ptr; // Store the location of a call - reg addr; // used for memory operations - reg tmp; // used for temporary storage - reg r0; // 12 registers for now - reg r1; - reg r2; - reg r3; - reg r4; - reg r5; - reg r6; - reg r7; - reg r8; - reg r9; - reg r10; - reg r11; - - // ============== iszero check for X ======================= - col witness XInv; - col witness XIsZero; - XIsZero = 1 - X * XInv; - XIsZero * X = 0; - XIsZero * (1 - XIsZero) = 0; - - - // =============== read-write memory ======================= - // Read-write memory. Columns are sorted by m_addr and - // then by m_step. m_change is 1 if and only if m_addr changes - // in the next row. - col witness m_addr; - col witness m_step; - col witness m_change; - col witness m_value; - // If we have an operation at all (needed because this needs to be a permutation) - col witness m_op; - // If the operation is a write operation. - col witness m_is_write; - col witness m_is_read; - - // positive numbers (assumed to be much smaller than the field order) - col fixed POSITIVE(i) { i + 1 }; - col fixed FIRST = [1] + [0]*; - col fixed LAST(i) { FIRST(i + 1) }; - col fixed STEP(i) { i }; - - m_change * (1 - m_change) = 0; - - // if m_change is zero, m_addr has to stay the same. - (m_addr' - m_addr) * (1 - m_change) = 0; - - // Except for the last row, if m_change is 1, then m_addr has to increase, - // if it is zero, m_step has to increase. - (1 - LAST) { m_change * (m_addr' - m_addr) + (1 - m_change) * (m_step' - m_step) } in POSITIVE; - - m_op * (1 - m_op) = 0; - m_is_write * (1 - m_is_write) = 0; - m_is_read * (1 - m_is_read) = 0; - // m_is_write can only be 1 if m_op is 1. - m_is_write * (1 - m_op) = 0; - m_is_read * (1 - m_op) = 0; - m_is_read * m_is_write = 0; - - - - - - // If the next line is a read and we stay at the same address, then the - // value cannot change. - (1 - m_is_write') * (1 - m_change) * (m_value' - m_value) = 0; - - // If the next line is a read and we have an address change, - // then the value is zero. - (1 - m_is_write') * m_change * m_value' = 0; - - - // ============== memory instructions ============== - instr store X { { addr, STEP, X } is m_is_write { m_addr, m_step, m_value } } - instr load -> X { { addr, STEP, X } is m_is_read { m_addr, m_step, m_value } } - - - - /// Add - /// Take in two input registers, send the result into the output register - instr add Y, Z -> X { - X = Y + Z - } - - /// Sub - instr sub Y, Z -> X { - X = Y - Z - } - - /// Is the value equal to 0 - uses only assignment registers - instr eq X -> Y { Y = XIsZero } - - /// Mul - instr mul Y, Z -> X { - X = Y * Z - } - - /// move - // TODO: move should zero out the sending register? - instr mov Y -> X { - X = Y - } - - // When we get a call, we want - instr call l: label { pc' = l, jump_ptr' = pc + 1 } - instr ret { pc' = jump_ptr } - - /// Jumps - instr jump l: label { pc' = l } - instr jumpi X, l: label { pc' = (1 - XIsZero) * l + XIsZero * (pc + 1) } - instr jumpni X, l: label { pc' = XIsZero * l + (1 - XIsZero) * (pc + 1) } - - - - function main { - r0 <=X= 0; - r1 <=X= 0; - call bbdf; - return; - bbdf:: - r3 <=X= 3; - ret; - } -} diff --git a/bberg/bytecode.acir b/bberg/bytecode.acir deleted file mode 100644 index b4019fc537..0000000000 --- a/bberg/bytecode.acir +++ /dev/null @@ -1 +0,0 @@ -H4sIAAAAAAAA/61SQQrAMAjTuXV0h73Fsg/0/68aMoVQvM1cDApJGipExPRBgCNiN33qPwyu01LMuwG/fPZlb2jAZ1GOVt+THpC1J++8/S61vo957a4lSY9xOyn5L0UhRgijAS8FGF5CSMECwQIAAA== \ No newline at end of file diff --git a/bberg/node_modules/.yarn-integrity b/bberg/node_modules/.yarn-integrity deleted file mode 100644 index ae931097fe..0000000000 --- a/bberg/node_modules/.yarn-integrity +++ /dev/null @@ -1,10 +0,0 @@ -{ - "systemParams": "linux-x64-108", - "modulesFolders": [], - "flags": [], - "linkedModules": [], - "topLevelPatterns": [], - "lockfileEntries": {}, - "files": [], - "artifacts": {} -} \ No newline at end of file diff --git a/bberg/src/circuit_builder.rs b/bberg/src/circuit_builder.rs index 357ab692ce..d2f5bf036c 100644 --- a/bberg/src/circuit_builder.rs +++ b/bberg/src/circuit_builder.rs @@ -1,179 +1,19 @@ -use std::collections::HashSet; -use std::fs::File; -use std::io::Write; +use ast::analyzed::Analyzed; -use ast::parsed::SelectedExpressions; -// use acvm::acir::native_types::Expression; -use ast::analyzed::Identity; use itertools::Itertools; - -use ast::analyzed::{ - AlgebraicBinaryOperator, AlgebraicExpression as Expression, AlgebraicUnaryOperator, Analyzed, - IdentityKind, -}; - -use number::{DegreeType, FieldElement}; - -// use super::circuit_data::CircuitData; - +use number::FieldElement; use pil_analyzer::pil_analyzer::inline_intermediate_polynomials; +use crate::file_writer::BBFiles; use crate::prover_builder::{prover_builder_cpp, prover_builder_hpp}; +use crate::relation_builder::{create_identities, create_relation_hpp, create_row_type}; +use crate::trace_builder::TraceBuilder; use crate::verifier_builder::{verifier_builder_cpp, verifier_builder_hpp}; -use crate::FILE_NAME; use crate::{ composer_builder::{composer_builder_cpp, composer_builder_hpp}, flavor_builder, - trace_builder::TraceBuilder, }; -pub struct BBFiles { - pub relation_hpp: Option, - pub arith_hpp: Option, - pub flavor_hpp: Option, - // trace - pub trace_hpp: Option, - // composer - pub composer_cpp: Option, - pub composer_hpp: Option, - - // prover - pub prover_cpp: Option, - pub prover_hpp: Option, - - // verifier - pub verifier_cpp: Option, - pub verifier_hpp: Option, - - // Relative paths - pub file_name: String, - pub base: String, - pub rel: String, - pub arith: String, - pub trace: String, - pub flavor: String, - pub composer: String, - pub prover: String, // path for both prover and verifier files -} - -impl BBFiles { - pub fn default(file_name: String) -> Self { - Self::new(file_name, None, None, None, None, None, None, None) - } - - pub fn new( - file_name: String, - base: Option, - rel: Option, - arith: Option, - trace: Option, - flavor: Option, - composer: Option, - prover: Option, - ) -> Self { - let base = base.unwrap_or("src/barretenberg".to_owned()); - let rel = rel.unwrap_or("proof_system/relations/generated".to_owned()); - let arith = arith.unwrap_or("proof_system/arithmetization/generated".to_owned()); - let trace = trace.unwrap_or("proof_system/circuit_builder/generated".to_owned()); - let flavor = flavor.unwrap_or("honk/flavor/generated".to_owned()); - let composer = composer.unwrap_or("honk/composer/generated".to_owned()); - let prover = prover.unwrap_or("honk/proof_system/generated".to_owned()); - - Self { - file_name, - relation_hpp: None, - arith_hpp: None, - flavor_hpp: None, - trace_hpp: None, - composer_cpp: None, - composer_hpp: None, - prover_cpp: None, - prover_hpp: None, - verifier_cpp: None, - verifier_hpp: None, - - base, - rel, - arith, - trace, - flavor, - composer, - prover, - } - } - - pub fn add_files( - &mut self, - relation_hpp: String, - arith_hpp: String, - trace_hpp: String, - flavor_hpp: String, - composer_cpp: String, - composer_hpp: String, - verifier_cpp: String, - verifier_hpp: String, - prover_cpp: String, - prover_hpp: String, - ) { - self.relation_hpp = Some(relation_hpp); - self.arith_hpp = Some(arith_hpp); - self.flavor_hpp = Some(flavor_hpp); - self.composer_cpp = Some(composer_cpp); - self.composer_hpp = Some(composer_hpp); - - self.trace_hpp = Some(trace_hpp); - - self.verifier_cpp = Some(verifier_cpp); - self.verifier_hpp = Some(verifier_hpp); - - self.prover_cpp = Some(prover_cpp); - self.prover_hpp = Some(prover_hpp); - } - - pub fn write(&self) { - // Helper macro codegen using the classes' write_file method - macro_rules! write_file { - ($location:expr, $extension:expr, $content:expr) => { - self.write_file( - &$location, - &format!("{}{}", self.file_name, $extension), - &$content.clone().unwrap(), - ); - }; - } - write_file!(self.rel, ".hpp", self.relation_hpp); - write_file!(self.arith, "_arith.hpp", self.arith_hpp); - - // Trace - write_file!(self.trace, "_trace.hpp", self.trace_hpp); - - write_file!(self.flavor, "_flavor.hpp", self.flavor_hpp); - - // Composer - write_file!(self.composer, "_composer.hpp", self.composer_hpp); - write_file!(self.composer, "_composer.cpp", self.composer_cpp); - - // Prover - write_file!(self.prover, "_prover.hpp", self.prover_hpp); - write_file!(self.prover, "_prover.cpp", self.prover_cpp); - - // Verifier - write_file!(self.prover, "_verifier.hpp", self.verifier_hpp); - write_file!(self.prover, "_verifier.cpp", self.verifier_cpp); - } - - fn write_file(&self, folder: &str, filename: &str, contents: &String) { - // attempt to create dir - let base_path = format!("{}/{}", self.base, folder); - let _ = std::fs::create_dir_all(&base_path); - - let joined = format!("{}/{}", base_path, filename); - println!("Writing file: {}", joined); - let mut file = File::create(joined).unwrap(); - file.write_all(contents.as_bytes()).unwrap(); - } -} - pub(crate) fn analyzed_to_cpp( analyzed: &Analyzed, fixed: &[(&str, Vec)], @@ -187,17 +27,15 @@ pub(crate) fn analyzed_to_cpp( // Collect all column names and determine if they need a shift or not // TODO: currently we provide shifts for both the fixed and witness columns, in the long term we need to work out what needs a shift and what doesn't - let fixed_names = fixed + let _fixed_names = fixed .iter() .map(|(name, _)| (*name).to_owned()) .collect::>(); - let witness_names = witness + let _witness_names = witness .iter() .map(|(name, _)| (*name).to_owned()) .collect::>(); - println!("Fixed: {:?}", fixed_names); - println!("Witness: {:?}", witness_names); let first_col = fixed .iter() .find(|col_name| col_name.0.contains("FIRST")) @@ -218,7 +56,6 @@ pub(crate) fn analyzed_to_cpp( let (subrelations, identities, mut collected_shifts) = create_identities(&first_col, &last_col, &analyzed_identities); let shifted_polys: Vec = collected_shifts.drain().collect_vec(); - dbg!(shifted_polys.clone()); let (all_cols, unshifted, to_be_shifted, _shifted, all_cols_with_shifts) = get_all_col_names(fixed, witness, &shifted_polys); @@ -294,122 +131,6 @@ namespace arithmetization {{ ) } -fn create_relation_hpp( - name: &str, - sub_relations: &[String], - identities: &[BBIdentity], - row_type: &String, - all_rows_and_shifts: &[String], -) -> String { - let includes = relation_includes(); - let class_boilerplate = relation_class_boilerplate(name, sub_relations, identities); - let export = get_export(name); - - let view_macro_preamble = get_cols_in_identity_macro(all_rows_and_shifts); - - format!( - "{includes} -namespace proof_system::{name}_vm {{ - -{row_type}; - -{view_macro_preamble} - -{class_boilerplate} - -{export} - - }}" - ) -} - -fn relation_class_boilerplate( - name: &str, - sub_relations: &[String], - identities: &[BBIdentity], -) -> String { - // TODO: MOVE ELSEWHERE: We add one to all degrees because we have an extra scaling factor - let degrees = identities.iter().map(|(d, _)| d + 1).collect(); - let degree_boilerplate = get_degree_boilerplate(degrees); - let relation_code = get_relation_code(sub_relations); - format!( - "template class {name}Impl {{ - public: - using FF = FF_; - - {degree_boilerplate} - - {relation_code} -}};", - ) -} - -fn get_export(name: &str) -> String { - format!( - "template using {name} = Relation<{name}Impl>;", - name = name - ) -} - -fn get_relation_code(ids: &[String]) -> String { - let mut relation_code = r#" - template - void static accumulate( - ContainerOverSubrelations& evals, - const AllEntities& new_term, - [[maybe_unused]] const RelationParameters&, - [[maybe_unused]] const FF& scaling_factor - ){ - - "# - .to_owned(); - for id in ids { - relation_code.push_str(&format!("{}\n", id)); - } - relation_code.push_str("}\n"); - relation_code -} - -fn get_degree_boilerplate(degrees: Vec) -> String { - // TODO: for the meantime we will use the max degree for all, i am facing a compile time issue with cpp - // that is preventing me from using the real degree - let max = degrees.iter().max().unwrap(); - let num_degrees = degrees.len(); - - let mut degree_boilerplate = - format!("static constexpr std::array SUBRELATION_LENGTHS{{\n"); - // for i in 0..degrees.len() { - // degree_boilerplate.push_str(&format!(" {},\n", degrees[i])); - // } - for _ in 0..degrees.len() { - degree_boilerplate.push_str(&format!(" {},\n", max)); - } - degree_boilerplate.push_str("};"); - - degree_boilerplate -} - -// As all of our rows are annotated, we should be able to create -// the row type by hand here -// The row type is a combination of the fixed and witness columns - -// The include statements required for a new relation file -fn relation_includes() -> &'static str { - r#" -#pragma once -#include "../relation_parameters.hpp" -#include "../relation_types.hpp" -"# -} - -// Yucky that everything is allocated into vecs here -fn create_row_type_items(names: &[String]) -> Vec { - names - .iter() - .map(|name| format!(" FF {} {{}};", name.replace('.', "_"))) - .collect::>() -} - fn get_all_col_names( fixed: &[(&str, Vec)], witness: &[(&str, Vec)], @@ -446,8 +167,6 @@ fn get_all_col_names( .flatten() .collect(); - dbg!(all_cols.clone()); - let unshifted: Vec = [fixed_names.clone(), witness_names.clone()] .into_iter() .flatten() @@ -467,191 +186,3 @@ fn get_all_col_names( with_shifts, ) } - -// Each vm will need to have a row which is a combination of all of the witness columns -fn create_row_type(all_rows: &[String]) -> String { - let all_annotated = create_row_type_items(all_rows); - - let row_type = format!( - "template struct Row {{ \n{}\n }}", - all_annotated.join("\n"), - ); - - println!("{}", row_type); - row_type -} - -fn get_cols_in_identity_macro(all_rows_and_shifts: &[String]) -> String { - let make_view_per_row = all_rows_and_shifts - .iter() - .map(|row_name| { - let name = row_name.replace('.', "_"); - format!("[[maybe_unused]] auto {name} = View(new_term.{name}); \\") - }) - .collect::>() - .join("\n"); - - format!( - " - #define DECLARE_VIEWS(index) \ - using View = typename std::tuple_element::type; \ - {make_view_per_row} - - - - - " - ) -} - -fn create_identity( - last_col: &str, - expression: &SelectedExpressions>, - collected_shifts: &mut HashSet, -) -> Option { - // We want to read the types of operators and then create the appropiate code - - if let Some(expr) = &expression.selector { - let x = craft_expression(last_col, expr, collected_shifts); - println!("{:?}", x); - Some(x) - } else { - None - } -} - -// TODO: replace the preamble with a macro so the code looks nicer -fn create_subrelation( - first_col: &str, - index: usize, - preamble: String, - identity: &mut BBIdentity, -) -> String { - // \\\ - let id = &identity.1; - - // TODO: TEMP HACK: Part of the main_FIRST hack below - to switch off constraints on the first row - identity.0 += 1; - format!( - "//Contribution {index} - {{\n{preamble} - - auto tmp = {id}; - tmp *= scaling_factor; - tmp *= (-{first_col} + FF(1)); // Temp to switch off - std::get<{index}>(evals) += tmp; -}}", - ) -} - -fn craft_expression( - last_col: &str, - expr: &Expression, - collected_shifts: &mut HashSet, -) -> BBIdentity { - match expr { - Expression::Number(n) => (1, format!("FF({})", n.to_arbitrary_integer())), - Expression::Reference(polyref) => { - assert_eq!(polyref.index, None); - let mut poly_name = polyref.name.replace('.', "_").to_string(); - let mut degree = 1; - if polyref.next { - // NOTE: Naive algorithm to collect all shifted polys - collected_shifts.insert(poly_name.clone()); - - poly_name = format!("{}_shift", poly_name); - - // TODO(HORRIBLE): TEMP, add in a relation that turns off shifts on the last row - poly_name = format!("{poly_name} * (-{} + FF(1))", last_col); - degree += 1; - } - (degree, poly_name) - } - Expression::BinaryOperation(lhe, op, rhe) => { - let (ld, lhs) = craft_expression(last_col, lhe, collected_shifts); - let (rd, rhs) = craft_expression(last_col, rhe, collected_shifts); - - // dbg!(&lhe); - let degree = std::cmp::max(ld, rd); - match op { - AlgebraicBinaryOperator::Add => (degree, format!("({} + {})", lhs, rhs)), - AlgebraicBinaryOperator::Sub => match lhe.as_ref() { - // BBerg hack, we do not want a field on the lhs of an expression - Expression::Number(_) => (degree, format!("(-{} + {})", rhs, lhs)), - _ => (degree, format!("({} - {})", lhs, rhs)), - }, - - AlgebraicBinaryOperator::Mul => (degree + 1, format!("({} * {})", lhs, rhs)), - _ => unimplemented!("{:?}", expr), - } - } - // Expression::Constant(name) => { - // panic!("Constant {name} was not inlined. optimize_constants needs to be run at least.") - // } - // pub enum UnaryOperator { - // Plus, - // Minus, - // LogicalNot, - // } - Expression::UnaryOperation(operator, expression) => match operator { - AlgebraicUnaryOperator::Minus => { - let (d, e) = craft_expression(last_col, expression, collected_shifts); - (d, format!("-{}", e)) - } - _ => unimplemented!("{:?}", expr), - }, - - _ => unimplemented!("{:?}", expr), - } -} - -// TODO: MOve -> to gen code we need to know the degree of each poly -type BBIdentity = (DegreeType, String); - -/// Todo, eventually these will need to be siloed based on the file name they are in -fn create_identities( - first_col: &str, - last_col: &str, - identities: &Vec>>, -) -> (Vec, Vec, HashSet) { - // We only want the expressions for now - // When we have a poly type, we only need the left side of it - let expressions = identities - .iter() - .filter_map(|identity| { - if identity.kind == IdentityKind::Polynomial { - Some(identity.left.clone()) - } else { - None - } - }) - .collect::>(); - - let mut identities = Vec::new(); - let mut subrelations = Vec::new(); - let mut collected_shifts: HashSet = HashSet::new(); - - for (i, expression) in expressions.iter().enumerate() { - let relation_boilerplate = format!( - "DECLARE_VIEWS({i}); - ", - ); - // TODO: deal with unwrap - - let mut identity = create_identity(last_col, expression, &mut collected_shifts).unwrap(); - let subrelation = create_subrelation(first_col, i, relation_boilerplate, &mut identity); - - identities.push(identity); - - subrelations.push(subrelation); - } - - // Returning both for now - (subrelations, identities, collected_shifts) -} - -// -// Row check_row = { .main_FIRST = 1, .main__block_enforcer_last_step = 1, .main_XIsZero = 1 }; -// rows.push_back(check_row); -// -// diff --git a/bberg/src/file_writer.rs b/bberg/src/file_writer.rs new file mode 100644 index 0000000000..97ae6bb3ff --- /dev/null +++ b/bberg/src/file_writer.rs @@ -0,0 +1,149 @@ +use std::fs::File; +use std::io::Write; + +pub struct BBFiles { + pub relation_hpp: Option, + pub arith_hpp: Option, + pub flavor_hpp: Option, + // trace + pub trace_hpp: Option, + // composer + pub composer_cpp: Option, + pub composer_hpp: Option, + + // prover + pub prover_cpp: Option, + pub prover_hpp: Option, + + // verifier + pub verifier_cpp: Option, + pub verifier_hpp: Option, + + // Relative paths + pub file_name: String, + pub base: String, + pub rel: String, + pub arith: String, + pub trace: String, + pub flavor: String, + pub composer: String, + pub prover: String, // path for both prover and verifier files +} + +impl BBFiles { + pub fn default(file_name: String) -> Self { + Self::new(file_name, None, None, None, None, None, None, None) + } + + pub fn new( + file_name: String, + base: Option, + rel: Option, + arith: Option, + trace: Option, + flavor: Option, + composer: Option, + prover: Option, + ) -> Self { + let base = base.unwrap_or("src/barretenberg".to_owned()); + let rel = rel.unwrap_or("proof_system/relations/generated".to_owned()); + let arith = arith.unwrap_or("proof_system/arithmetization/generated".to_owned()); + let trace = trace.unwrap_or("proof_system/circuit_builder/generated".to_owned()); + let flavor = flavor.unwrap_or("honk/flavor/generated".to_owned()); + let composer = composer.unwrap_or("honk/composer/generated".to_owned()); + let prover = prover.unwrap_or("honk/proof_system/generated".to_owned()); + + Self { + file_name, + relation_hpp: None, + arith_hpp: None, + flavor_hpp: None, + trace_hpp: None, + composer_cpp: None, + composer_hpp: None, + prover_cpp: None, + prover_hpp: None, + verifier_cpp: None, + verifier_hpp: None, + + base, + rel, + arith, + trace, + flavor, + composer, + prover, + } + } + + pub fn add_files( + &mut self, + relation_hpp: String, + arith_hpp: String, + trace_hpp: String, + flavor_hpp: String, + composer_cpp: String, + composer_hpp: String, + verifier_cpp: String, + verifier_hpp: String, + prover_cpp: String, + prover_hpp: String, + ) { + self.relation_hpp = Some(relation_hpp); + self.arith_hpp = Some(arith_hpp); + self.flavor_hpp = Some(flavor_hpp); + self.composer_cpp = Some(composer_cpp); + self.composer_hpp = Some(composer_hpp); + + self.trace_hpp = Some(trace_hpp); + + self.verifier_cpp = Some(verifier_cpp); + self.verifier_hpp = Some(verifier_hpp); + + self.prover_cpp = Some(prover_cpp); + self.prover_hpp = Some(prover_hpp); + } + + pub fn write(&self) { + // Helper macro codegen using the classes' write_file method + macro_rules! write_file { + ($location:expr, $extension:expr, $content:expr) => { + self.write_file( + &$location, + &format!("{}{}", self.file_name, $extension), + &$content.clone().unwrap(), + ); + }; + } + write_file!(self.rel, ".hpp", self.relation_hpp); + write_file!(self.arith, "_arith.hpp", self.arith_hpp); + + // Trace + write_file!(self.trace, "_trace.hpp", self.trace_hpp); + + write_file!(self.flavor, "_flavor.hpp", self.flavor_hpp); + + // Composer + write_file!(self.composer, "_composer.hpp", self.composer_hpp); + write_file!(self.composer, "_composer.cpp", self.composer_cpp); + + // Prover + write_file!(self.prover, "_prover.hpp", self.prover_hpp); + write_file!(self.prover, "_prover.cpp", self.prover_cpp); + + // Verifier + write_file!(self.prover, "_verifier.hpp", self.verifier_hpp); + write_file!(self.prover, "_verifier.cpp", self.verifier_cpp); + } + + fn write_file(&self, folder: &str, filename: &str, contents: &String) { + // attempt to create dir + let base_path = format!("{}/{}", self.base, folder); + let _ = std::fs::create_dir_all(&base_path); + + let joined = format!("{}/{}", base_path, filename); + println!("Writing file: {}", joined); + let mut file = File::create(joined).unwrap(); + file.write_all(contents.as_bytes()).unwrap(); + } +} diff --git a/bberg/src/lib.rs b/bberg/src/lib.rs index 53b369f443..d6cdb794d4 100644 --- a/bberg/src/lib.rs +++ b/bberg/src/lib.rs @@ -1,10 +1,9 @@ pub mod bberg_codegen; pub mod circuit_builder; -pub mod composer_builder; -pub mod flavor_builder; -pub mod prover_builder; -pub mod relation_builder; -pub mod trace_builder; -pub mod verifier_builder; - -const FILE_NAME: &str = "Fib"; +mod composer_builder; +mod file_writer; +mod flavor_builder; +mod prover_builder; +mod relation_builder; +mod trace_builder; +mod verifier_builder; diff --git a/bberg/src/main.rs b/bberg/src/main.rs deleted file mode 100644 index 17cf630fef..0000000000 --- a/bberg/src/main.rs +++ /dev/null @@ -1,246 +0,0 @@ -use acvm::acir::brillig::Opcode as BrilligOpcode; -use acvm::acir::circuit::Circuit; -use acvm::acir::circuit::Opcode; -use acvm::brillig_vm::brillig::BinaryFieldOp; -use acvm::brillig_vm::brillig::Label; -use acvm::brillig_vm::brillig::RegisterIndex; - -use rand::Rng; -use std::fs; -use std::io::Write; -use std::iter; - -use std::collections::HashMap; - -/// Module to convert brillig assmebly into powdr assembly - -// struct BrilligArchitecture {} - -// impl Architecture for BrilligArchitecture { -// fn instruction_ends_control_flow(instr: &str) -> bool { -// match instr { -// "li" | "lui" | "la" | "mv" | "add" | "addi" | "sub" | "neg" | "mul" | "mulhu" -// | "divu" | "xor" | "xori" | "and" | "andi" | "or" | "ori" | "not" | "slli" | "sll" -// | "srli" | "srl" | "srai" | "seqz" | "snez" | "slt" | "slti" | "sltu" | "sltiu" -// | "sgtz" | "beq" | "beqz" | "bgeu" | "bltu" | "blt" | "bge" | "bltz" | "blez" -// | "bgtz" | "bgez" | "bne" | "bnez" | "jal" | "jalr" | "call" | "ecall" | "ebreak" -// | "lw" | "lb" | "lbu" | "lh" | "lhu" | "sw" | "sh" | "sb" | "nop" | "fence" -// | "fence.i" | "amoadd.w.rl" | "amoadd.w" => false, -// "j" | "jr" | "tail" | "ret" | "trap" => true, -// _ => { -// panic!("Unknown instruction: {instr}"); -// } -// } -// } - -// fn get_references<'a, R: asm_utils::ast::Register, F: asm_utils::ast::FunctionOpKind>( -// instr: &str, -// args: &'a [asm_utils::ast::Argument], -// ) -> Vec<&'a str> { -// // fence arguments are not symbols, they are like reserved -// // keywords affecting the instruction behavior -// if instr.starts_with("fence") { -// Vec::new() -// } else { -// symbols_in_args(args) -// } -// } -// } - -fn main() { - // Read in file called bytecode.acir - let bytecode = fs::read("bytecode.acir").expect("Unable to read file"); - // Convert the read-in base64 file into Vec - let decoded = base64::decode(bytecode).expect("Failed to decode base64"); - let bytecode = decoded; - - // Create a new circuit from the bytecode instance - let circuit: Circuit = - Circuit::deserialize_circuit(&bytecode).expect("Failed to deserialize circuit"); - - println!("circuit: {:?}", circuit); - - // Get the brillig opcodes - let brillig = extract_brillig(circuit.opcodes); - print!("{:?}", brillig); - - let preamble = get_preamble(); - let program = construct_main(brillig); - let powdr = brillig_machine(&preamble, program); - - println!("powdr: {:?}", powdr); - - // temp write the output to a file - let mut file = fs::File::create("brillig_out.asm").expect("Could not create file"); - file.write_all(powdr.as_bytes()) - .expect("Could not write to file"); -} - -fn brillig_machine( - // machines: &[&str], - preamble: &str, - // submachines: &[(&str, &str)], - program: Vec, -) -> String { - format!( - r#" -machine Main {{ - -{} - - function main {{ -{} - }} -}} -"#, - preamble, - program - .into_iter() - .map(|line| format!("\t\t{line}")) - .collect::>() - .join("\n") - ) -} - -// Output the powdr assembly with the given circuit -fn construct_main(program: Opcode) -> Vec { - let mut main_asm: Vec = Vec::new(); - - // For each instruction in brillig, we want o - let trace = match program { - Opcode::Brillig(brillig) => brillig.bytecode, - _ => { - panic!("Opcode is not of type brillig"); - } - }; - - println!(); - println!(); - trace.iter().for_each(|i| println!("{:?}", i)); - println!(); - println!(); - - // Label of [index], String, where index is the generated name of the jump, we will place a jump label there when - // we encounter it to prove - let mut labels: HashMap = HashMap::new(); - - for (index, instr) in trace.into_iter().enumerate() { - println!("{:?}", instr); - println!(); - println!(); - println!(); - // powdr_asm.push_str(&instr.to_string()); - - // If we require a label to be placed at the jump location then we add it - if let Some(jump) = labels.get(&index) { - main_asm.push(format!("{}::", jump)); - } - - match instr { - BrilligOpcode::Const { destination, value } => { - let number = value.to_usize().to_string(); - main_asm.push(format!("{} <=X= {};", print_register(destination), number)); - } - BrilligOpcode::Stop => { - main_asm.push("return;".to_owned()); - } - BrilligOpcode::Return => { - main_asm.push("ret;".to_owned()); - } - // Calls -> look more into how this is performed internally - // For calls we want to store the current pc in a holding register such that we can return to it - // We then want to jump to that location in the bytecode - BrilligOpcode::Call { location } => { - // Generate a label for the location we are going to - let label = gen_label(); - labels.insert(location, label.clone()); // This label will be inserted later on! - - main_asm.push(format!("call {};", label)); - } - BrilligOpcode::BinaryFieldOp { - destination, - op, - lhs, - rhs, - } => { - // Match the given operation - match op { - BinaryFieldOp::Add => { - main_asm.push(format!( - "{} <== add({}, {});", - print_register(destination), - print_register(lhs), - print_register(rhs) - )); - } - BinaryFieldOp::Sub => { - main_asm.push(format!( - "{} <== sub({}, {});", - print_register(destination), - print_register(lhs), - print_register(rhs) - )); - } - // Equals is currently a mix of the equals instruction and the using the X is 0 witness column - BinaryFieldOp::Equals => { - main_asm.push(format!( - "tmp <== sub({}, {});", - print_register(lhs), - print_register(rhs) - )); - main_asm.push(format!("{} <== eq(tmp);", print_register(destination),)); - } - BinaryFieldOp::Mul => { - main_asm.push(format!( - "{} <== mul({}, {});", - print_register(destination), - print_register(lhs), - print_register(rhs) - )); - } - // TODO: div - _ => println!("not implemented"), - } - } - _ => println!("not implemented"), - } - } - - println!("main_asm: {:?}", main_asm); - - main_asm -} - -fn gen_label() -> String { - let mut rng = rand::thread_rng(); - let hex_chars: Vec = "abcdef".chars().collect(); - // Lmao chat gpt fix - let label: String = iter::repeat(()) - .map(|()| rng.gen_range(0..hex_chars.len())) - .map(|i| hex_chars[i]) - .take(4) - .collect(); - - label -} - -fn print_register(r_index: RegisterIndex) -> String { - let num = r_index.to_usize(); - format!("r{}", num).to_owned() -} - -// Read the preamble from the brillig.asm machine -fn get_preamble() -> String { - fs::read_to_string("brillig.asm").expect("Unable to read file") -} - -fn extract_brillig(opcodes: Vec) -> Opcode { - if opcodes.len() != 1 { - panic!("There should only be one brillig opcode"); - } - let opcode = &opcodes[0]; - if opcode.name() != "brillig" { - panic!("Opcode is not of type brillig"); - } - opcode.clone() -} diff --git a/bberg/src/relation_builder.rs b/bberg/src/relation_builder.rs index 8b13789179..94704ce6df 100644 --- a/bberg/src/relation_builder.rs +++ b/bberg/src/relation_builder.rs @@ -1 +1,314 @@ +use ast::analyzed::Identity; +use ast::analyzed::{ + AlgebraicBinaryOperator, AlgebraicExpression as Expression, AlgebraicUnaryOperator, + IdentityKind, +}; +use ast::parsed::SelectedExpressions; +use itertools::Itertools; +use std::collections::HashSet; +use number::{DegreeType, FieldElement}; + +// TODO: MOve -> to gen code we need to know the degree of each poly +type BBIdentity = (DegreeType, String); + +pub(crate) fn create_relation_hpp( + name: &str, + sub_relations: &[String], + identities: &[BBIdentity], + row_type: &String, + all_rows_and_shifts: &[String], +) -> String { + let includes = relation_includes(); + let class_boilerplate = relation_class_boilerplate(name, sub_relations, identities); + let export = get_export(name); + + let view_macro_preamble = get_cols_in_identity_macro(all_rows_and_shifts); + + format!( + "{includes} +namespace proof_system::{name}_vm {{ + +{row_type}; + +{view_macro_preamble} + +{class_boilerplate} + +{export} + + }}" + ) +} + +fn relation_class_boilerplate( + name: &str, + sub_relations: &[String], + identities: &[BBIdentity], +) -> String { + // TODO: MOVE ELSEWHERE: We add one to all degrees because we have an extra scaling factor + let degrees = identities.iter().map(|(d, _)| d + 1).collect(); + let degree_boilerplate = get_degree_boilerplate(degrees); + let relation_code = get_relation_code(sub_relations); + format!( + "template class {name}Impl {{ + public: + using FF = FF_; + + {degree_boilerplate} + + {relation_code} +}};", + ) +} + +fn get_export(name: &str) -> String { + format!( + "template using {name} = Relation<{name}Impl>;", + name = name + ) +} + +fn get_relation_code(ids: &[String]) -> String { + let mut relation_code = r#" + template + void static accumulate( + ContainerOverSubrelations& evals, + const AllEntities& new_term, + [[maybe_unused]] const RelationParameters&, + [[maybe_unused]] const FF& scaling_factor + ){ + + "# + .to_owned(); + for id in ids { + relation_code.push_str(&format!("{}\n", id)); + } + relation_code.push_str("}\n"); + relation_code +} + +fn get_degree_boilerplate(degrees: Vec) -> String { + // TODO: for the meantime we will use the max degree for all, i am facing a compile time issue with cpp + // that is preventing me from using the real degree + let max = degrees.iter().max().unwrap(); + let num_degrees = degrees.len(); + + let mut degree_boilerplate = + format!("static constexpr std::array SUBRELATION_LENGTHS{{\n"); + // for i in 0..degrees.len() { + // degree_boilerplate.push_str(&format!(" {},\n", degrees[i])); + // } + for _ in 0..degrees.len() { + degree_boilerplate.push_str(&format!(" {},\n", max)); + } + degree_boilerplate.push_str("};"); + + degree_boilerplate +} + +// As all of our rows are annotated, we should be able to create +// the row type by hand here +// The row type is a combination of the fixed and witness columns + +// The include statements required for a new relation file +fn relation_includes() -> &'static str { + r#" +#pragma once +#include "../relation_parameters.hpp" +#include "../relation_types.hpp" +"# +} + +// Yucky that everything is allocated into vecs here +fn create_row_type_items(names: &[String]) -> Vec { + names + .iter() + .map(|name| format!(" FF {} {{}};", name.replace('.', "_"))) + .collect::>() +} + +// Each vm will need to have a row which is a combination of all of the witness columns +pub(crate) fn create_row_type(all_rows: &[String]) -> String { + let all_annotated = create_row_type_items(all_rows); + + let row_type = format!( + "template struct Row {{ \n{}\n }}", + all_annotated.join("\n"), + ); + + println!("{}", row_type); + row_type +} + +fn get_cols_in_identity_macro(all_rows_and_shifts: &[String]) -> String { + let make_view_per_row = all_rows_and_shifts + .iter() + .map(|row_name| { + let name = row_name.replace('.', "_"); + format!("[[maybe_unused]] auto {name} = View(new_term.{name}); \\") + }) + .collect::>() + .join("\n"); + + format!( + " + #define DECLARE_VIEWS(index) \ + using View = typename std::tuple_element::type; \ + {make_view_per_row} + + + + + " + ) +} + +fn create_identity( + last_col: &str, + expression: &SelectedExpressions>, + collected_shifts: &mut HashSet, +) -> Option { + // We want to read the types of operators and then create the appropiate code + + if let Some(expr) = &expression.selector { + let x = craft_expression(last_col, expr, collected_shifts); + println!("{:?}", x); + Some(x) + } else { + None + } +} + +// TODO: replace the preamble with a macro so the code looks nicer +fn create_subrelation( + first_col: &str, + index: usize, + preamble: String, + identity: &mut BBIdentity, +) -> String { + // \\\ + let id = &identity.1; + + // TODO: TEMP HACK: Part of the main_FIRST hack below - to switch off constraints on the first row + identity.0 += 1; + format!( + "//Contribution {index} + {{\n{preamble} + + auto tmp = {id}; + tmp *= scaling_factor; + tmp *= (-{first_col} + FF(1)); // Temp to switch off + std::get<{index}>(evals) += tmp; +}}", + ) +} + +fn craft_expression( + last_col: &str, + expr: &Expression, + collected_shifts: &mut HashSet, +) -> BBIdentity { + match expr { + Expression::Number(n) => (1, format!("FF({})", n.to_arbitrary_integer())), + Expression::Reference(polyref) => { + assert_eq!(polyref.index, None); + let mut poly_name = polyref.name.replace('.', "_").to_string(); + let mut degree = 1; + if polyref.next { + // NOTE: Naive algorithm to collect all shifted polys + collected_shifts.insert(poly_name.clone()); + + poly_name = format!("{}_shift", poly_name); + + // TODO(HORRIBLE): TEMP, add in a relation that turns off shifts on the last row + poly_name = format!("{poly_name} * (-{} + FF(1))", last_col); + degree += 1; + } + (degree, poly_name) + } + Expression::BinaryOperation(lhe, op, rhe) => { + let (ld, lhs) = craft_expression(last_col, lhe, collected_shifts); + let (rd, rhs) = craft_expression(last_col, rhe, collected_shifts); + + // dbg!(&lhe); + let degree = std::cmp::max(ld, rd); + match op { + AlgebraicBinaryOperator::Add => (degree, format!("({} + {})", lhs, rhs)), + AlgebraicBinaryOperator::Sub => match lhe.as_ref() { + // BBerg hack, we do not want a field on the lhs of an expression + Expression::Number(_) => (degree, format!("(-{} + {})", rhs, lhs)), + _ => (degree, format!("({} - {})", lhs, rhs)), + }, + + AlgebraicBinaryOperator::Mul => (degree + 1, format!("({} * {})", lhs, rhs)), + _ => unimplemented!("{:?}", expr), + } + } + // Expression::Constant(name) => { + // panic!("Constant {name} was not inlined. optimize_constants needs to be run at least.") + // } + // pub enum UnaryOperator { + // Plus, + // Minus, + // LogicalNot, + // } + Expression::UnaryOperation(operator, expression) => match operator { + AlgebraicUnaryOperator::Minus => { + let (d, e) = craft_expression(last_col, expression, collected_shifts); + (d, format!("-{}", e)) + } + _ => unimplemented!("{:?}", expr), + }, + + _ => unimplemented!("{:?}", expr), + } +} + +/// Todo, eventually these will need to be siloed based on the file name they are in +pub(crate) fn create_identities( + first_col: &str, + last_col: &str, + identities: &Vec>>, +) -> (Vec, Vec, HashSet) { + // We only want the expressions for now + // When we have a poly type, we only need the left side of it + let expressions = identities + .iter() + .filter_map(|identity| { + if identity.kind == IdentityKind::Polynomial { + Some(identity.left.clone()) + } else { + None + } + }) + .collect::>(); + + let mut identities = Vec::new(); + let mut subrelations = Vec::new(); + let mut collected_shifts: HashSet = HashSet::new(); + + for (i, expression) in expressions.iter().enumerate() { + let relation_boilerplate = format!( + "DECLARE_VIEWS({i}); + ", + ); + // TODO: deal with unwrap + + let mut identity = create_identity(last_col, expression, &mut collected_shifts).unwrap(); + let subrelation = create_subrelation(first_col, i, relation_boilerplate, &mut identity); + + identities.push(identity); + + subrelations.push(subrelation); + } + + // Returning both for now + (subrelations, identities, collected_shifts) +} + +// +// Row check_row = { .main_FIRST = 1, .main__block_enforcer_last_step = 1, .main_XIsZero = 1 }; +// rows.push_back(check_row); +// +// diff --git a/bberg/src/trace_builder.rs b/bberg/src/trace_builder.rs index 9c5f3551db..a3e987fe11 100644 --- a/bberg/src/trace_builder.rs +++ b/bberg/src/trace_builder.rs @@ -1,4 +1,4 @@ -use crate::circuit_builder::BBFiles; +use crate::file_writer::BBFiles; pub trait TraceBuilder { fn create_trace_builder_cpp( diff --git a/bberg/tut/hello.asm b/bberg/tut/hello.asm deleted file mode 100644 index e3974219a2..0000000000 --- a/bberg/tut/hello.asm +++ /dev/null @@ -1,39 +0,0 @@ -machine HelloWorld { - - degree 8; - - reg pc[@pc]; - reg X[<=]; // assignment registers - reg Y[<=]; - reg A; // floating registers - - instr incr X -> Y { - Y = X + 1 - } - - instr decr X -> Y { - Y = X -1 - } - - // How do we stop this from being an assignment - // rather than a comparison, as the instruction looks the same - instr assert_zero X { - X = 0 - } - - // ============== memory instructions ============== - instr mstore X { { addr, STEP, X } is m_is_write { m_addr, m_step, m_value } } - instr mload -> X { { addr, STEP, X } is m_is_read { m_addr, m_step, m_value } } - - constraints { - - } - - function main { - A <=X= ${ ("input", 0)}; // This is a built in function to take args from the command line - A <== incr(A); - A <== decr(A); - assert_zero A; - return; - } -} \ No newline at end of file