-
Notifications
You must be signed in to change notification settings - Fork 380
chore: add acir-gen unit tests per ssa instruction #8084
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
d688861
add unit tests for each ssa instructions
guipublic a651926
Merge branch 'master' into gd/acir-gen_tests
guipublic f3b2941
Merge branch 'master' into gd/acir-gen_tests
guipublic 01ee97f
Code review
guipublic a591bf1
code review
guipublic d5e131e
Merge branch 'master' into gd/acir-gen_tests
guipublic dd8ca5e
merge from master
guipublic f4c073d
code review
guipublic 98020d3
Merge branch 'master' into gd/acir-gen_tests
guipublic 79abf60
use ssa interpreter for comparing with acvm execution
guipublic a0142dd
Merge branch 'master' into gd/acir-gen_tests
guipublic cb5c611
fix merge from master
guipublic dea6531
clippy
guipublic bafb5d7
Merge branch 'master' into gd/acir-gen_tests
guipublic ae06bbb
Merge branch 'master' into gd/acir-gen_tests
guipublic 740428d
code review
guipublic 22d9246
Merge branch 'master' into gd/acir-gen_tests
guipublic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ use crate::{ | |
| brillig::{Brillig, BrilligOptions, brillig_ir::artifact::GeneratedBrillig}, | ||
| ssa::{ | ||
| function_builder::FunctionBuilder, | ||
| interpreter::value::Value, | ||
| ir::{ | ||
| function::FunctionId, | ||
| instruction::BinaryOp, | ||
|
|
@@ -924,3 +925,208 @@ fn do_not_overflow_with_constant_constrain_neq() { | |
| assert_eq!(acir_functions.len(), 1); | ||
| assert!(acir_functions[0].opcodes().is_empty()); | ||
| } | ||
|
|
||
| /// Convert the SSA input into ACIR and use ACVM to execute it | ||
| /// Returns the ACVM execution status and the value of the 'output' witness value, | ||
| /// unless the provided output is None or the ACVM fails during execution. | ||
| fn execute_ssa( | ||
| ssa: Ssa, | ||
| initial_witness: WitnessMap<FieldElement>, | ||
| output: Option<&Witness>, | ||
| ) -> (ACVMStatus<FieldElement>, Option<FieldElement>) { | ||
| let brillig = ssa.to_brillig(&BrilligOptions::default()); | ||
| let (acir_functions, brillig_functions, _, _) = ssa | ||
| .into_acir(&brillig, &BrilligOptions::default(), ExpressionWidth::default()) | ||
| .expect("Should compile manually written SSA into ACIR"); | ||
| assert_eq!(acir_functions.len(), 1); | ||
| let main = &acir_functions[0]; | ||
| let mut acvm = ACVM::new( | ||
| &StubbedBlackBoxSolver(true), | ||
| main.opcodes(), | ||
| initial_witness, | ||
| &brillig_functions, | ||
| &[], | ||
| ); | ||
| let status = acvm.solve(); | ||
| if status == ACVMStatus::Solved { | ||
| (status, output.map(|o| acvm.witness_map()[o])) | ||
| } else { | ||
| (status, None) | ||
| } | ||
| } | ||
|
|
||
| fn get_main_src(typ: &str) -> String { | ||
| format!( | ||
| "acir(inline) fn main f0 {{ | ||
| b0(inputs: [{typ}; 2]): | ||
| lhs = array_get inputs, index u32 0 -> {typ} | ||
| rhs = array_get inputs, index u32 1 -> {typ} | ||
| " | ||
| ) | ||
| } | ||
|
|
||
| /// Create a SSA instruction corresponding to the operator, using v1 and v2 as operands. | ||
| /// Additional information can be added to the string, | ||
| /// for instance, "range_check 8" creates 'range_check v1 to 8 bits' | ||
| fn generate_test_instruction_from_operator(operator: &str) -> (String, bool) { | ||
| let ops = operator.split(" ").collect::<Vec<_>>(); | ||
| let op = ops[0]; | ||
| let mut output = true; | ||
| let src = match op { | ||
| "constrain" => { | ||
| output = false; | ||
| format!("constrain lhs {} rhs", ops[1]) | ||
| } | ||
| "not" => format!("result = {} lhs", op), | ||
| "truncate" => { | ||
| format!("result = truncate lhs to {} bits, max_bit_size: {}", ops[1], ops[2]) | ||
| } | ||
| "range_check" => { | ||
| output = false; | ||
| format!("range_check lhs to {} bits", ops[1]) | ||
| } | ||
| _ => format!("result = {} lhs, rhs", op), | ||
| }; | ||
|
|
||
| if output { | ||
| ( | ||
| format!( | ||
| " | ||
| {src} | ||
| return result | ||
| }}" | ||
| ), | ||
| true, | ||
| ) | ||
| } else { | ||
| ( | ||
| format!( | ||
| " | ||
| {src} | ||
| return | ||
| }}" | ||
| ), | ||
| false, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| /// Execute a simple operation for each operators | ||
| /// The operation is executed from SSA IR using ACVM after acir-gen | ||
| /// and also directly on the SSA IR using the SSA interpreter. | ||
| /// The results are compared to ensure that both executions yield the same result. | ||
| fn test_operators( | ||
| // The list of operators to test | ||
| operators: &[&str], | ||
| // the type of the input values | ||
| typ: &str, | ||
| // the values of the inputs | ||
| inputs: &[FieldElement], | ||
| ) { | ||
| let main = get_main_src(typ); | ||
| let num_type = match typ.chars().next().unwrap() { | ||
| 'F' => NumericType::NativeField, | ||
| 'i' => NumericType::Signed { bit_size: typ[1..].parse().unwrap() }, | ||
| 'u' => NumericType::Unsigned { bit_size: typ[1..].parse().unwrap() }, | ||
| _ => unreachable!("invalid numeric type"), | ||
| }; | ||
| let inputs_int = Value::array_from_iter(inputs.iter().cloned(), num_type).unwrap(); | ||
| let inputs = | ||
| inputs.iter().enumerate().map(|(i, f)| (Witness(i as u32), *f)).collect::<BTreeMap<_, _>>(); | ||
| let len = inputs.len() as u32; | ||
| let initial_witness = WitnessMap::from(inputs); | ||
|
|
||
| for op in operators { | ||
| let (src, with_output) = generate_test_instruction_from_operator(op); | ||
| let output = if with_output { Some(Witness(len)) } else { None }; | ||
| let ssa = Ssa::from_str(&(main.to_owned() + &src)).unwrap(); | ||
| // ssa execution | ||
| let ssa_interpreter_result = ssa.interpret(vec![inputs_int.clone()]); | ||
| // acir execution | ||
| let acir_execution_result = execute_ssa(ssa, initial_witness.clone(), output.as_ref()); | ||
|
|
||
| match (ssa_interpreter_result, acir_execution_result) { | ||
| // Both execution failed, so it is the same behavior, as expected. | ||
| (Err(_), (ACVMStatus::Failure(_), _)) => (), | ||
| // Both execution succeeded and output the same value | ||
| (Ok(ssa_inner_result), (ACVMStatus::Solved, acvm_result)) => { | ||
| let ssa_result = if let Some(result) = ssa_inner_result.first() { | ||
| result.as_numeric().map(|v| v.convert_to_field()) | ||
| } else { | ||
| None | ||
| }; | ||
| assert_eq!(ssa_result, acvm_result); | ||
| } | ||
| _ => panic!("ssa and acvm execution should have the same result"), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_binary_on_field() { | ||
| // Test the following Binary operation on Fields | ||
| let operators = [ | ||
| "add", | ||
| "sub", | ||
| "mul", | ||
| "div", | ||
| "eq", | ||
| // Bitwise operations are not allowed on field elements | ||
| // SSA interpreter will emit an error but not ACVM | ||
| // "and", | ||
| // "xor", | ||
| "unchecked_add", | ||
| "unchecked_sub", | ||
| "unchecked_mul", | ||
| "range_check 32", | ||
| "truncate 32 254", | ||
| ]; | ||
| let inputs = [FieldElement::from(1_usize), FieldElement::from(2_usize)]; | ||
| test_operators(&operators, "Field", &inputs); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_u32() { | ||
| // Test the following operations on u32 | ||
| let operators = [ | ||
| "add", | ||
| "sub", | ||
| "mul", | ||
| "div", | ||
| "eq", | ||
| "and", | ||
| "xor", | ||
| "unchecked_add", | ||
| "unchecked_sub", | ||
| "unchecked_mul", | ||
| "mod", | ||
| "lt", | ||
| "or", | ||
| "not", | ||
| "range_check 8", | ||
| "truncate 8 32", | ||
| ]; | ||
| let inputs = [FieldElement::from(2_usize), FieldElement::from(1_usize)]; | ||
| test_operators(&operators, "u32", &inputs); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess in follow-ups we can test the remaining numeric types then. |
||
|
|
||
| // "unchecked_sub" 300 - 500 is not a valid ssa instruction because it assumes no over/under flow. | ||
| // it is translated into 300 - 500 in ACVM, which gives -200 | ||
| // it is translated into 300 - 500 2**64 in SSA interpreter | ||
| let operators = ["unchecked_add", "unchecked_mul", "range_check 8", "truncate 8 32"]; | ||
| let inputs = [FieldElement::from(300_usize), FieldElement::from(500_usize)]; | ||
| test_operators(&operators, "u32", &inputs); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_constraint() { | ||
| let operators = ["constrain ==", "constrain !="]; | ||
| // Test constraints on Fields with distinct inputs | ||
| test_operators( | ||
| &operators, | ||
| "Field", | ||
| &[FieldElement::from(1_usize), FieldElement::from(2_usize)], | ||
| ); | ||
|
|
||
| // u32, equal inputs | ||
| test_operators(&operators, "u32", &[FieldElement::from(2_usize), FieldElement::from(2_usize)]); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can follow-up with expected error tests that would have corresponding issues.