Skip to content

Commit

Permalink
[VAN-545] extract loop body to separate function before unrolling (#52)
Browse files Browse the repository at this point in the history
* Make PassMemory mutability more fine-grained. Needed to allow certain fields to be mutated during the run/execute visitor pattern that previously required a borrow on the entire PassMemory object.
* Implement extracting the body of loops to a new function prior to unrolling
* [VAN-609] inline the array indexing/identity functions
* [VAN-673]
* [VAN-677]
* Fix scalar return source functions
* Refactor loop unroll into multiple files
* Move memory.rs to interpreter folder
* Remove unused JoinSemiLattice
* Refactor Env and add different cases, refactor ContextSwitcher
* Add 'counter_override' for extracted loop body function to access subcmp counter
* Use different LLVM IR label for flattened loops vs branches
* Update/add more tests
* Documentation
* Code cleanup
  • Loading branch information
tim-hoffman authored Oct 18, 2023
1 parent e1d4a39 commit 7150c67
Show file tree
Hide file tree
Showing 89 changed files with 7,727 additions and 1,263 deletions.
48 changes: 46 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 10 additions & 17 deletions circom/src/compilation_user.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use ansi_term::Colour;
use circuit_passes::passes::PassManager;
use compiler::compiler_interface;
use compiler::compiler_interface::{Config, VCP};
use compiler::compiler_interface::{self, Config, VCP};
use program_structure::error_definition::Report;
use program_structure::error_code::ReportCode;
use program_structure::file_definition::FileLibrary;
use program_structure::program_archive::ProgramArchive;
use crate::VERSION;


pub struct CompilerConfig {
pub js_folder: String,
pub wasm_name: String,
Expand Down Expand Up @@ -61,28 +59,24 @@ pub fn compile(config: CompilerConfig, program_archive: ProgramArchive, prime: &
}

if config.llvm_flag {
// Only run this passes if we are going to generate LLVM code
// Only run the passes if we are going to generate LLVM code
let pm = PassManager::new();
circuit = pm
.schedule_loop_unroll_pass(prime)
.schedule_conditional_flattening_pass(prime)
.schedule_mapped_to_indexed_pass(prime)
.schedule_unknown_index_sanitization_pass(prime)
.schedule_simplification_pass(prime)
.schedule_deterministic_subcmp_invoke_pass(prime)
.transform_circuit(circuit);
.schedule_loop_unroll_pass()
.schedule_conditional_flattening_pass()
.schedule_mapped_to_indexed_pass()
.schedule_unknown_index_sanitization_pass()
.schedule_simplification_pass()
.schedule_deterministic_subcmp_invoke_pass()
.transform_circuit(circuit, prime);
compiler_interface::write_llvm_ir(
&mut circuit,
&program_archive,
&config.llvm_folder,
&config.llvm_file,
config.clean_llvm,
)?;
println!(
"{} {}",
Colour::Green.paint("Written successfully:"),
config.llvm_file
);
println!("{} {}", Colour::Green.paint("Written successfully:"), config.llvm_file);
}

match (config.wat_flag, config.wasm_flag) {
Expand Down Expand Up @@ -124,7 +118,6 @@ pub fn compile(config: CompilerConfig, program_archive: ProgramArchive, prime: &
Ok(())
}


fn wat_to_wasm(wat_file: &str, wasm_file: &str) -> Result<(), Report> {
use std::fs::read_to_string;
use std::fs::File;
Expand Down
167 changes: 150 additions & 17 deletions circom/tests/loops/call_inside_loop.circom
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
pragma circom 2.0.0;
// REQUIRES: circom
// RUN: rm -rf %t && mkdir %t && %circom --llvm -o %t %s | sed -n 's/.*Written successfully:.* \(.*\)/\1/p' | xargs cat | FileCheck %s
// XFAIL: .*
// RUN: rm -rf %t && mkdir %t && %circom --llvm -o %t %s | sed -n 's/.*Written successfully:.* \(.*\)/\1/p' | xargs cat | FileCheck %s --enable-var-scope

//arena = { a[0], a[1], n, b, c, d, e, f, g}
function fun(a, n, b, c, d, e, f, g) {
var x[5];
for (var i = 0; i < n; i++) {
Expand All @@ -11,6 +11,8 @@ function fun(a, n, b, c, d, e, f, g) {
return x[0] + x[2] + x[4];
}

//signal_arena = { out, in }
//lvars = { m, n, a[0], a[1], b[0], b[1], i }
template CallInLoop(n, m) {
signal input in;
signal output out;
Expand All @@ -27,18 +29,6 @@ template CallInLoop(n, m) {

component main = CallInLoop(2, 3);

//// Use the block labels to check that the loop is NOT unrolled
//CHECK-LABEL: define i256 @fun_{{[0-9]+}}
//CHECK-SAME: (i256* %[[ARG:[0-9]+]])
//CHECK-NOT: unrolled_loop{{.*}}:
//CHECK: loop.cond{{.*}}:
//CHECK: loop.body{{.*}}:
//CHECK: loop.end{{.*}}:
//CHECK-NOT: unrolled_loop{{.*}}:
//CHECK: }

//signal_arena = { out, in }
//lvars = { m, n, a[0], a[1], i, b[0], b[1] }
//
// var a[2];
// i = 0;
Expand All @@ -54,6 +44,149 @@ component main = CallInLoop(2, 3);
// i = 2;
// out <-- b[0];
//
//CHECK-LABEL: define void @CallInLoop_{{[0-9]+}}_run
//CHECK-SAME: ([0 x i256]* %[[ARG:[0-9]+]])
//CHECK: TODO: Code produced currently is incorrect! See https://veridise.atlassian.net/browse/VAN-611
//CHECK-LABEL: define void @..generated..loop.body.
//CHECK-SAME: [[$F_ID_1:[0-9]+]]([0 x i256]* %lvars, [0 x i256]* %signals, i256* %fix_0){{.*}} {
//CHECK-NEXT: ..generated..loop.body.[[$F_ID_1]]:
//CHECK-NEXT: br label %store1
//CHECK-EMPTY:
//CHECK-NEXT: store1:
//CHECK-NEXT: %0 = getelementptr [0 x i256], [0 x i256]* %signals, i32 0, i32 1
//CHECK-NEXT: %1 = load i256, i256* %0, align 4
//CHECK-NEXT: %call.fr_add = call i256 @fr_add(i256 3, i256 %1)
//CHECK-NEXT: %2 = getelementptr i256, i256* %fix_0, i32 0
//CHECK-NEXT: store i256 %call.fr_add, i256* %2, align 4
//CHECK-NEXT: br label %store2
//CHECK-EMPTY:
//CHECK-NEXT: store2:
//CHECK-NEXT: %3 = getelementptr [0 x i256], [0 x i256]* %lvars, i32 0, i32 4
//CHECK-NEXT: %4 = load i256, i256* %3, align 4
//CHECK-NEXT: %call.fr_add1 = call i256 @fr_add(i256 %4, i256 1)
//CHECK-NEXT: %5 = getelementptr [0 x i256], [0 x i256]* %lvars, i32 0, i32 4
//CHECK-NEXT: store i256 %call.fr_add1, i256* %5, align 4
//CHECK-NEXT: br label %return3
//CHECK-EMPTY:
//CHECK-NEXT: return3:
//CHECK-NEXT: ret void
//CHECK-NEXT: }
//
//CHECK-LABEL: define void @..generated..loop.body.
//CHECK-SAME: [[$F_ID_2:[0-9]+]]([0 x i256]* %lvars, [0 x i256]* %signals){{.*}} {
//CHECK-NEXT: ..generated..loop.body.[[$F_ID_2]]:
//CHECK-NEXT: br label %call1
//CHECK-EMPTY:
//CHECK-NEXT: call1:
//CHECK-NEXT: %fun_0_arena = alloca [15 x i256], align 8
//CHECK-NEXT: %0 = getelementptr [15 x i256], [15 x i256]* %fun_0_arena, i32 0, i32 0
//CHECK-NEXT: %1 = getelementptr [0 x i256], [0 x i256]* %lvars, i32 0, i32 2
//CHECK-NEXT: call void @fr_copy_n(i256* %1, i256* %0, i32 2)
//CHECK-NEXT: %2 = getelementptr [15 x i256], [15 x i256]* %fun_0_arena, i32 0, i32 2
//CHECK-NEXT: store i256 2, i256* %2, align 4
//CHECK-NEXT: %3 = getelementptr [15 x i256], [15 x i256]* %fun_0_arena, i32 0, i32 3
//CHECK-NEXT: store i256 3, i256* %3, align 4
//CHECK-NEXT: %4 = getelementptr [15 x i256], [15 x i256]* %fun_0_arena, i32 0, i32 4
//CHECK-NEXT: store i256 3, i256* %4, align 4
//CHECK-NEXT: %5 = getelementptr [15 x i256], [15 x i256]* %fun_0_arena, i32 0, i32 5
//CHECK-NEXT: store i256 3, i256* %5, align 4
//CHECK-NEXT: %6 = getelementptr [15 x i256], [15 x i256]* %fun_0_arena, i32 0, i32 6
//CHECK-NEXT: store i256 3, i256* %6, align 4
//CHECK-NEXT: %7 = getelementptr [15 x i256], [15 x i256]* %fun_0_arena, i32 0, i32 7
//CHECK-NEXT: store i256 3, i256* %7, align 4
//CHECK-NEXT: %8 = getelementptr [15 x i256], [15 x i256]* %fun_0_arena, i32 0, i32 8
//CHECK-NEXT: store i256 3, i256* %8, align 4
//CHECK-NEXT: %9 = bitcast [15 x i256]* %fun_0_arena to i256*
//CHECK-NEXT: %call.fun_0 = call i256 @fun_0(i256* %9)
//CHECK-NEXT: %10 = getelementptr [0 x i256], [0 x i256]* %lvars, i32 0, i32 6
//CHECK-NEXT: %11 = load i256, i256* %10, align 4
//CHECK-NEXT: %call.fr_cast_to_addr = call i32 @fr_cast_to_addr(i256 %11)
//CHECK-NEXT: %mul_addr = mul i32 1, %call.fr_cast_to_addr
//CHECK-NEXT: %add_addr = add i32 %mul_addr, 4
//CHECK-NEXT: %12 = getelementptr [0 x i256], [0 x i256]* %lvars, i32 0, i32 %add_addr
//CHECK-NEXT: store i256 %call.fun_0, i256* %12, align 4
//CHECK-NEXT: br label %store2
//CHECK-EMPTY:
//CHECK-NEXT: store2:
//CHECK-NEXT: %13 = getelementptr [0 x i256], [0 x i256]* %lvars, i32 0, i32 6
//CHECK-NEXT: %14 = load i256, i256* %13, align 4
//CHECK-NEXT: %call.fr_add = call i256 @fr_add(i256 %14, i256 1)
//CHECK-NEXT: %15 = getelementptr [0 x i256], [0 x i256]* %lvars, i32 0, i32 6
//CHECK-NEXT: store i256 %call.fr_add, i256* %15, align 4
//CHECK-NEXT: br label %return3
//CHECK-EMPTY:
//CHECK-NEXT: return3:
//CHECK-NEXT: ret void
//CHECK-NEXT: }
//
//CHECK-LABEL: define void @CallInLoop_{{[0-9]+}}_run([0 x i256]* %0){{.*}} {
//CHECK-NEXT: prelude:
//CHECK-NEXT: %lvars = alloca [7 x i256], align 8
//CHECK-NEXT: %subcmps = alloca [0 x { [0 x i256]*, i32 }], align 8
//CHECK-NEXT: br label %store1
//CHECK-EMPTY:
//CHECK-NEXT: store1:
//CHECK-NEXT: %1 = getelementptr [7 x i256], [7 x i256]* %lvars, i32 0, i32 0
//CHECK-NEXT: store i256 3, i256* %1, align 4
//CHECK-NEXT: br label %store2
//CHECK-EMPTY:
//CHECK-NEXT: store2:
//CHECK-NEXT: %2 = getelementptr [7 x i256], [7 x i256]* %lvars, i32 0, i32 1
//CHECK-NEXT: store i256 2, i256* %2, align 4
//CHECK-NEXT: br label %store3
//CHECK-EMPTY:
//CHECK-NEXT: store3:
//CHECK-NEXT: %3 = getelementptr [7 x i256], [7 x i256]* %lvars, i32 0, i32 2
//CHECK-NEXT: store i256 0, i256* %3, align 4
//CHECK-NEXT: br label %store4
//CHECK-EMPTY:
//CHECK-NEXT: store4:
//CHECK-NEXT: %4 = getelementptr [7 x i256], [7 x i256]* %lvars, i32 0, i32 3
//CHECK-NEXT: store i256 0, i256* %4, align 4
//CHECK-NEXT: br label %store5
//CHECK-EMPTY:
//CHECK-NEXT: store5:
//CHECK-NEXT: %5 = getelementptr [7 x i256], [7 x i256]* %lvars, i32 0, i32 4
//CHECK-NEXT: store i256 0, i256* %5, align 4
//CHECK-NEXT: br label %unrolled_loop6
//CHECK-EMPTY:
//CHECK-NEXT: unrolled_loop6:
//CHECK-NEXT: %6 = bitcast [7 x i256]* %lvars to [0 x i256]*
//CHECK-NEXT: %7 = bitcast [7 x i256]* %lvars to [0 x i256]*
//CHECK-NEXT: %8 = getelementptr [0 x i256], [0 x i256]* %7, i32 0, i256 2
//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %6, [0 x i256]* %0, i256* %8)
//CHECK-NEXT: %9 = bitcast [7 x i256]* %lvars to [0 x i256]*
//CHECK-NEXT: %10 = bitcast [7 x i256]* %lvars to [0 x i256]*
//CHECK-NEXT: %11 = getelementptr [0 x i256], [0 x i256]* %10, i32 0, i256 3
//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %9, [0 x i256]* %0, i256* %11)
//CHECK-NEXT: br label %store7
//CHECK-EMPTY:
//CHECK-NEXT: store7:
//CHECK-NEXT: %12 = getelementptr [7 x i256], [7 x i256]* %lvars, i32 0, i32 4
//CHECK-NEXT: store i256 0, i256* %12, align 4
//CHECK-NEXT: br label %store8
//CHECK-EMPTY:
//CHECK-NEXT: store8:
//CHECK-NEXT: %13 = getelementptr [7 x i256], [7 x i256]* %lvars, i32 0, i32 5
//CHECK-NEXT: store i256 0, i256* %13, align 4
//CHECK-NEXT: br label %store9
//CHECK-EMPTY:
//CHECK-NEXT: store9:
//CHECK-NEXT: %14 = getelementptr [7 x i256], [7 x i256]* %lvars, i32 0, i32 6
//CHECK-NEXT: store i256 0, i256* %14, align 4
//CHECK-NEXT: br label %unrolled_loop10
//CHECK-EMPTY:
//CHECK-NEXT: unrolled_loop10:
//CHECK-NEXT: %15 = bitcast [7 x i256]* %lvars to [0 x i256]*
//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_2]]([0 x i256]* %15, [0 x i256]* %0)
//CHECK-NEXT: %16 = bitcast [7 x i256]* %lvars to [0 x i256]*
//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_2]]([0 x i256]* %16, [0 x i256]* %0)
//CHECK-NEXT: br label %store11
//CHECK-EMPTY:
//CHECK-NEXT: store11:
//CHECK-NEXT: %17 = getelementptr [7 x i256], [7 x i256]* %lvars, i32 0, i32 4
//CHECK-NEXT: %18 = load i256, i256* %17, align 4
//CHECK-NEXT: %19 = getelementptr [0 x i256], [0 x i256]* %0, i32 0, i32 0
//CHECK-NEXT: store i256 %18, i256* %19, align 4
//CHECK-NEXT: br label %prologue
//CHECK-EMPTY:
//CHECK-NEXT: prologue:
//CHECK-NEXT: ret void
//CHECK-NEXT: }
2 changes: 1 addition & 1 deletion circom/tests/loops/fib_input.circom
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pragma circom 2.0.0;
// REQUIRES: circom
// RUN: rm -rf %t && mkdir %t && %circom --llvm -o %t %s | sed -n 's/.*Written successfully:.* \(.*\)/\1/p' | xargs cat | FileCheck %s
// RUN: rm -rf %t && mkdir %t && %circom --llvm -o %t %s | sed -n 's/.*Written successfully:.* \(.*\)/\1/p' | xargs cat | FileCheck %s --enable-var-scope

template Fibonacci() {
signal input nth_fib;
Expand Down
2 changes: 1 addition & 1 deletion circom/tests/loops/fib_template.circom
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pragma circom 2.0.0;
// REQUIRES: circom
// RUN: rm -rf %t && mkdir %t && %circom --llvm -o %t %s | sed -n 's/.*Written successfully:.* \(.*\)/\1/p' | xargs cat | FileCheck %s
// RUN: rm -rf %t && mkdir %t && %circom --llvm -o %t %s | sed -n 's/.*Written successfully:.* \(.*\)/\1/p' | xargs cat | FileCheck %s --enable-var-scope

template FibonacciTmpl(N) {
signal output out;
Expand Down
2 changes: 1 addition & 1 deletion circom/tests/loops/for_known.circom
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pragma circom 2.0.0;
// REQUIRES: circom
// RUN: rm -rf %t && mkdir %t && %circom --llvm -o %t %s | sed -n 's/.*Written successfully:.* \(.*\)/\1/p' | xargs cat | FileCheck %s
// RUN: rm -rf %t && mkdir %t && %circom --llvm -o %t %s | sed -n 's/.*Written successfully:.* \(.*\)/\1/p' | xargs cat | FileCheck %s --enable-var-scope

template ForKnown(N) {
signal output out;
Expand Down
2 changes: 1 addition & 1 deletion circom/tests/loops/for_unknown.circom
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pragma circom 2.0.0;
// REQUIRES: circom
// RUN: rm -rf %t && mkdir %t && %circom --llvm -o %t %s | sed -n 's/.*Written successfully:.* \(.*\)/\1/p' | xargs cat | FileCheck %s
// RUN: rm -rf %t && mkdir %t && %circom --llvm -o %t %s | sed -n 's/.*Written successfully:.* \(.*\)/\1/p' | xargs cat | FileCheck %s --enable-var-scope

template ForUnknown() {
signal input in;
Expand Down
2 changes: 1 addition & 1 deletion circom/tests/loops/for_unknown_index.circom
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pragma circom 2.0.0;
// REQUIRES: circom
// RUN: rm -rf %t && mkdir %t && %circom --llvm -o %t %s | sed -n 's/.*Written successfully:.* \(.*\)/\1/p' | xargs cat | FileCheck %s
// RUN: rm -rf %t && mkdir %t && %circom --llvm -o %t %s | sed -n 's/.*Written successfully:.* \(.*\)/\1/p' | xargs cat | FileCheck %s --enable-var-scope

template ForUnknownIndex() {
signal input in;
Expand Down
Loading

0 comments on commit 7150c67

Please sign in to comment.