diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index ce6938588..1c7648731 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -30,23 +30,23 @@ pub fn no_callback() -> Option Option> { /// Compiles a .pil or .asm file and runs witness generation. /// If the file ends in .asm, converts it to .pil first. +/// Returns the compilation result if any compilation took place. pub fn compile_pil_or_asm( file_name: &str, inputs: Vec, output_dir: &Path, force_overwrite: bool, prove_with: Option, -) -> PathBuf { +) -> Option> { if file_name.ends_with(".asm") { compile_asm(file_name, inputs, output_dir, force_overwrite, prove_with) } else { - compile_pil( + Some(compile_pil( Path::new(file_name), output_dir, Some(inputs_to_query_callback(inputs)), prove_with, - ); - PathBuf::from(file_name) + )) } } @@ -56,14 +56,14 @@ pub fn analyze_pil(pil_file: &Path) -> Analyzed { /// Compiles a .pil file to its json form and also tries to generate /// constants and committed polynomials. -/// @returns true if all committed/witness and constant/fixed polynomials -/// could be generated. +/// @returns a compilation result, containing witness and fixed columns +/// if they could be successfully generated. pub fn compile_pil( pil_file: &Path, output_dir: &Path, query_callback: Option, prove_with: Option, -) -> bool +) -> CompilationResult where QueryCallback: FnMut(&str) -> Option + Sync + Send, { @@ -76,13 +76,15 @@ where ) } +/// Compiles a given PIL and tries to generate fixed and witness columns. +/// @returns a compilation result, containing witness and fixed columns pub fn compile_pil_ast( pil: &PILFile, file_name: &OsStr, output_dir: &Path, query_callback: Option, prove_with: Option, -) -> bool +) -> CompilationResult where QueryCallback: FnMut(&str) -> Option + Sync + Send, { @@ -99,13 +101,14 @@ where /// Compiles a .asm file, outputs the PIL on stdout and tries to generate /// fixed and witness columns. +/// @returns a compilation result if any compilation was done. pub fn compile_asm( file_name: &str, inputs: Vec, output_dir: &Path, force_overwrite: bool, prove_with: Option, -) -> PathBuf { +) -> Option> { let contents = fs::read_to_string(file_name).unwrap(); compile_asm_string( file_name, @@ -115,12 +118,13 @@ pub fn compile_asm( force_overwrite, prove_with, ) + .1 } /// Compiles the contents of a .asm file, outputs the PIL on stdout and tries to generate /// fixed and witness columns. /// -/// Returns the relative pil file name. +/// Returns the relative pil file name and the compilation result if any compilation was done. pub fn compile_asm_string( file_name: &str, contents: &str, @@ -128,7 +132,7 @@ pub fn compile_asm_string( output_dir: &Path, force_overwrite: bool, prove_with: Option, -) -> PathBuf { +) -> (PathBuf, Option>) { let parsed = parser::parse_asm(Some(file_name), contents).unwrap_or_else(|err| { eprintln!("Error parsing .asm file:"); err.output_to_stderr(); @@ -149,28 +153,41 @@ pub fn compile_asm_string( "Target file {} already exists. Not overwriting.", pil_file_path.to_str().unwrap() ); - return pil_file_path; + return (pil_file_path, None); } fs::write(pil_file_path.clone(), format!("{pil}")).unwrap(); - compile_pil_ast( - &pil, - pil_file_path.file_name().unwrap(), - output_dir, - Some(inputs_to_query_callback(inputs)), - prove_with, - ); - - pil_file_path + let pil_file_name = pil_file_path.file_name().unwrap(); + ( + pil_file_path.clone(), + Some(compile_pil_ast( + &pil, + pil_file_name, + output_dir, + Some(inputs_to_query_callback(inputs)), + prove_with, + )), + ) } +pub struct CompilationResult { + /// Whether all committed/witness and constant/fixed polynomials could be generated. + pub success: bool, + /// Constant columns, potentially incomplete (if success is false) + pub constants: Vec<(String, Vec)>, + /// Witness columns, potentially None (if success is false) + pub witness: Option)>>, +} + +/// Optimizes a given pil and tries to generate constants and committed polynomials. +/// @returns a compilation result, containing witness and fixed columns, if successful. fn compile( analyzed: Analyzed, file_name: &OsStr, output_dir: &Path, query_callback: Option, prove_with: Option, -) -> bool +) -> CompilationResult where QueryCallback: FnMut(&str) -> Option + Send + Sync, { @@ -187,6 +204,8 @@ where log::info!("Evaluating fixed columns..."); let (constants, degree) = constant_evaluator::generate(&analyzed); log::info!("Took {}", start.elapsed().as_secs_f32()); + + let mut witness = None; if analyzed.constant_count() == constants.len() { write_constants_to_fs(&constants, output_dir, degree); log::info!("Generated constants."); @@ -200,10 +219,17 @@ where if let Some(Backend::Halo2) = prove_with { let degree = usize::BITS - degree.leading_zeros() + 1; let params = halo2::kzg_params(degree as usize); - let proof = halo2::prove_ast(&analyzed, constants, commits, params); + let proof = halo2::prove_ast(&analyzed, constants.clone(), commits.clone(), params); write_proof_to_fs(&proof, output_dir); log::info!("Generated proof."); } + + witness = Some( + commits + .into_iter() + .map(|(name, c)| (name.to_owned(), c)) + .collect(), + ); } else { log::warn!("Not writing constants.bin because not all declared constants are defined (or there are none)."); success = false; @@ -212,7 +238,16 @@ where write_compiled_json_to_fs(&json_out, file_name, output_dir); log::info!("Compiled PIL source code."); - success + let constants = constants + .into_iter() + .map(|(name, c)| (name.to_owned(), c)) + .collect(); + + CompilationResult { + success, + constants, + witness, + } } pub fn inputs_to_query_callback(inputs: Vec) -> impl Fn(&str) -> Option { diff --git a/compiler/src/verify.rs b/compiler/src/verify.rs index bdbd4e3d4..0de8a14a5 100644 --- a/compiler/src/verify.rs +++ b/compiler/src/verify.rs @@ -5,7 +5,7 @@ use crate::compile_asm_string; pub fn verify_asm_string(file_name: &str, contents: &str, inputs: Vec) { let temp_dir = mktemp::Temp::new_dir().unwrap(); - let pil_file_path = compile_asm_string(file_name, contents, inputs, &temp_dir, true, None); + let pil_file_path = compile_asm_string(file_name, contents, inputs, &temp_dir, true, None).0; let pil_file_name = pil_file_path.file_name().unwrap().to_string_lossy(); verify(&pil_file_name, &temp_dir); } diff --git a/compiler/tests/pil.rs b/compiler/tests/pil.rs index e084a9b19..55d22295b 100644 --- a/compiler/tests/pil.rs +++ b/compiler/tests/pil.rs @@ -10,12 +10,7 @@ pub fn verify_pil(file_name: &str, query_callback: Option Option { - let pil_filename = call_with_field!(compile_pil_or_asm::( - &file, - split_inputs(&inputs), - Path::new(&output_directory), + call_with_field!(compile_with_csv_export::( + file, + output_directory, + inputs, force, - prove_with + prove_with, + export_csv, + csv_mode )); - - if export_csv { - let pil = Path::new(&pil_filename); - let dir = Path::new(&output_directory); - let csv_path = dir.join("columns.csv"); - call_with_field!(export_columns_to_csv::( - pil, dir, &csv_path, csv_mode - )); - } } Commands::Prove { file, @@ -374,23 +371,47 @@ fn write_params_to_fs(params: &[u8], output_dir: &Path) { log::info!("Wrote params.bin."); } +fn compile_with_csv_export( + file: String, + output_directory: String, + inputs: String, + force: bool, + prove_with: Option, + export_csv: bool, + csv_mode: CsvRenderMode, +) { + let result = compile_pil_or_asm::( + &file, + split_inputs(&inputs), + Path::new(&output_directory), + force, + prove_with, + ); + + if export_csv { + // Compilation result is None if the ASM file has not been compiled + // (e.g. it has been compiled before and the force flag is not set) + if let Some(compilation_result) = result { + let csv_path = Path::new(&output_directory).join("columns.csv"); + export_columns_to_csv::( + compilation_result.constants, + compilation_result.witness, + &csv_path, + csv_mode, + ); + } + } +} + fn export_columns_to_csv( - file: &Path, - dir: &Path, + fixed: Vec<(String, Vec)>, + witness: Option)>>, csv_path: &Path, render_mode: CsvRenderMode, ) { - let pil = compiler::analyze_pil::(file); - let fixed = compiler::util::read_fixed(&pil, dir); - let witness = compiler::util::read_witness(&pil, dir); - - assert_eq!(fixed.1, witness.1); - let columns = fixed - .0 .into_iter() - .chain(witness.0.into_iter()) - .map(|(name, values)| (name.to_owned(), values)) + .chain(witness.unwrap_or(vec![]).into_iter()) .collect::>(); let mut csv_file = fs::File::create(csv_path).unwrap(); @@ -480,3 +501,44 @@ fn optimize_and_output(file: &str) { pilopt::optimize(compiler::analyze_pil::(Path::new(file))) ); } + +#[cfg(test)] +mod test { + + use backend::Backend; + use tempfile; + + use crate::{run_command, Commands, CsvRenderMode, FieldArgument}; + #[test] + fn test_simple_sum() { + let output_dir = tempfile::tempdir().unwrap(); + let output_dir_str = output_dir.path().to_string_lossy().to_string(); + + let pil_command = Commands::Pil { + file: "../test_data/asm/simple_sum.asm".into(), + field: FieldArgument::Bn254, + output_directory: output_dir_str.clone(), + inputs: "3,2,1,2".into(), + force: false, + prove_with: None, + export_csv: true, + csv_mode: CsvRenderMode::Hex, + }; + run_command(pil_command); + + let file = output_dir + .path() + .join("simple_sum_opt.pil") + .to_string_lossy() + .to_string(); + let prove_command = Commands::Prove { + file, + dir: output_dir_str, + field: FieldArgument::Bn254, + backend: Backend::Halo2Mock, + proof: None, + params: None, + }; + run_command(prove_command); + } +}