diff --git a/circom/tests/arrays/array8.circom b/circom/tests/arrays/array8.circom index 4931cd724..7dffc001e 100644 --- a/circom/tests/arrays/array8.circom +++ b/circom/tests/arrays/array8.circom @@ -30,7 +30,7 @@ component main = ArrayReturnTemplate(4); //CHECK-LABEL: define{{.*}} i256* @return_array_B_{{[0-9]+}} //CHECK-SAME: (i256* %[[ARENA:[0-9a-zA-Z_.]+]]){{.*}} { -//CHECK: %[[SRC_PTR:[0-9a-zA-Z_.]+]] = call i256* @return_array_A_{{[0-9]+}}(i256* %{{.*}}) +//CHECK: %[[SRC_PTR:[0-9a-zA-Z_.]+]] = call i256* @return_array_A_{{[0-9\.]+}}(i256* %{{.*}}) //CHECK-NEXT: %[[DST_PTR:[0-9a-zA-Z_.]+]] = getelementptr i256, i256* %[[ARENA]], i32 2 //CHECK-NEXT: %[[COPY_SRC_0:[0-9a-zA-Z_.]+]] = getelementptr i256, i256* %[[SRC_PTR]], i32 0 //CHECK-NEXT: %[[COPY_DST_0:[0-9a-zA-Z_.]+]] = getelementptr i256, i256* %[[DST_PTR]], i32 0 @@ -59,7 +59,7 @@ component main = ArrayReturnTemplate(4); //CHECK-NEXT: ret i256* %[[T10]] //CHECK-NEXT: } -//CHECK-LABEL: define{{.*}} i256* @return_array_A_{{[0-9]+}} +//CHECK-LABEL: define{{.*}} i256* @return_array_A_{{[0-9\.]+}} //CHECK-SAME: (i256* %[[ARENA:[0-9a-zA-Z_.]+]]){{.*}} { //CHECK: return{{[0-9]+}}: //CHECK-NEXT: %[[T0:[0-9a-zA-Z_.]+]] = getelementptr i256, i256* %[[ARENA]], i32 3 diff --git a/circom/tests/calls/call_arg_arraymulti.circom b/circom/tests/calls/call_arg_arraymulti.circom index 40bed300a..e08474999 100644 --- a/circom/tests/calls/call_arg_arraymulti.circom +++ b/circom/tests/calls/call_arg_arraymulti.circom @@ -49,9 +49,9 @@ component main = CallArgTest(); //CHECK-NEXT: ret void //CHECK-NEXT: } -//CHECK-LABEL: define{{.*}} i256 @sum_{{[0-9]+}} +//CHECK-LABEL: define{{.*}} i256 @sum_{{[0-9\.]+}} //CHECK-SAME: (i256* %[[T00:[0-9a-zA-Z_.]+]]){{.*}} { -//CHECK-NEXT: [[$FUN_NAME:sum_[0-9]+]]: +//CHECK-NEXT: [[$FUN_NAME:sum_[0-9\.]+]]: //CHECK-NEXT: br label %store1 //CHECK-EMPTY: //CHECK-NEXT: store1: diff --git a/circom/tests/calls/call_arg_arraymulti_arraymulti.circom b/circom/tests/calls/call_arg_arraymulti_arraymulti.circom index e56538e55..8bfea367d 100644 --- a/circom/tests/calls/call_arg_arraymulti_arraymulti.circom +++ b/circom/tests/calls/call_arg_arraymulti_arraymulti.circom @@ -53,9 +53,9 @@ component main = CallArgTest(); //CHECK-NEXT: ret void //CHECK-NEXT: } // -//CHECK-LABEL: define{{.*}} i256 @sum_{{[0-9]+}} +//CHECK-LABEL: define{{.*}} i256 @sum_{{[0-9\.]+}} //CHECK-SAME: (i256* %[[T00:[0-9a-zA-Z_.]+]]){{.*}} { -//CHECK-NEXT: [[$FUN_NAME:sum_[0-9]+]]: +//CHECK-NEXT: [[$FUN_NAME:sum_[0-9\.]+]]: //CHECK-NEXT: br label %store1 //CHECK-EMPTY: //CHECK-NEXT: store1: diff --git a/circom/tests/controlflow/early_return_loop_1.circom b/circom/tests/controlflow/early_return_loop_1.circom index dfe8b29d1..e3205c830 100644 --- a/circom/tests/controlflow/early_return_loop_1.circom +++ b/circom/tests/controlflow/early_return_loop_1.circom @@ -33,8 +33,8 @@ template EarlyReturn() { component main = EarlyReturn(); -//CHECK-LABEL: define{{.*}} i256 @noEarlyReturnFn_{{[0-9]+}}(i256* %0){{.*}} { -//CHECK-NEXT: noEarlyReturnFn_[[$F_ID_2:[0-9]+]]: +//CHECK-LABEL: define{{.*}} i256 @noEarlyReturnFn_{{[0-9\.]+}}(i256* %0){{.*}} { +//CHECK-NEXT: noEarlyReturnFn_[[$F_ID_2:[0-9\.]+]]: //CHECK-NEXT: br label %store1 //CHECK-EMPTY: //CHECK-NEXT: store1: diff --git a/circom/tests/controlflow/multiuse_func_with_loop.circom b/circom/tests/controlflow/multiuse_func_with_loop.circom new file mode 100644 index 000000000..b5c78bb7e --- /dev/null +++ b/circom/tests/controlflow/multiuse_func_with_loop.circom @@ -0,0 +1,349 @@ +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 --enable-var-scope + +// %0 = [ s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], n, sum, i ] +function f(s, n) { + var sum = 0; + for (var i = 0; i < n; i++) { + sum += s[i]; + } + return sum; +} + +template MultiUse() { + signal input inp[10]; + signal output outp[3]; + + outp[0] <-- f(inp, 2); + outp[1] <-- f(inp, 5); + outp[2] <-- f(inp, 9); +} + +component main = MultiUse(); + +//CHECK-LABEL: define{{.*}} void @..generated..loop.body. +//CHECK-SAME: [[$F_ID_1:[0-9]+]]([0 x i256]* %lvars, [0 x i256]* %signals, i256* %var_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]* %lvars, i32 0, i32 11 +//CHECK-NEXT: %1 = getelementptr [0 x i256], [0 x i256]* %lvars, i32 0, i32 11 +//CHECK-NEXT: %2 = load i256, i256* %1, align 4 +//CHECK-NEXT: %3 = getelementptr i256, i256* %var_0, i32 0 +//CHECK-NEXT: %4 = load i256, i256* %3, align 4 +//CHECK-NEXT: %call.fr_add = call i256 @fr_add(i256 %2, i256 %4) +//CHECK-NEXT: store i256 %call.fr_add, i256* %0, align 4 +//CHECK-NEXT: br label %store2 +//CHECK-EMPTY: +//CHECK-NEXT: store2: +//CHECK-NEXT: %5 = getelementptr [0 x i256], [0 x i256]* %lvars, i32 0, i32 12 +//CHECK-NEXT: %6 = getelementptr [0 x i256], [0 x i256]* %lvars, i32 0, i32 12 +//CHECK-NEXT: %7 = load i256, i256* %6, align 4 +//CHECK-NEXT: %call.fr_add1 = call i256 @fr_add(i256 %7, i256 1) +//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{{.*}} i256 @f_0.2(i256* %0){{.*}} { +//CHECK-NEXT: f_0.2: +//CHECK-NEXT: br label %store1 +//CHECK-EMPTY: +//CHECK-NEXT: store1: +//CHECK-NEXT: %1 = getelementptr i256, i256* %0, i32 11 +//CHECK-NEXT: store i256 0, i256* %1, align 4 +//CHECK-NEXT: br label %store2 +//CHECK-EMPTY: +//CHECK-NEXT: store2: +//CHECK-NEXT: %2 = getelementptr i256, i256* %0, i32 12 +//CHECK-NEXT: store i256 0, i256* %2, align 4 +//CHECK-NEXT: br label %unrolled_loop3 +//CHECK-EMPTY: +//CHECK-NEXT: unrolled_loop3: +//CHECK-NEXT: %3 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %4 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %5 = getelementptr [0 x i256], [0 x i256]* %4, i32 0, i256 0 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %3, [0 x i256]* null, i256* %5) +//CHECK-NEXT: %6 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %7 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %8 = getelementptr [0 x i256], [0 x i256]* %7, i32 0, i256 1 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %6, [0 x i256]* null, i256* %8) +//CHECK-NEXT: br label %return4 +//CHECK-EMPTY: +//CHECK-NEXT: return4: +//CHECK-NEXT: %9 = getelementptr i256, i256* %0, i32 11 +//CHECK-NEXT: %10 = load i256, i256* %9, align 4 +//CHECK-NEXT: ret i256 %10 +//CHECK-NEXT: } +// +//CHECK-LABEL: define{{.*}} i256 @f_0.5(i256* %0){{.*}} { +//CHECK-NEXT: f_0.5: +//CHECK-NEXT: br label %store1 +//CHECK-EMPTY: +//CHECK-NEXT: store1: +//CHECK-NEXT: %1 = getelementptr i256, i256* %0, i32 11 +//CHECK-NEXT: store i256 0, i256* %1, align 4 +//CHECK-NEXT: br label %store2 +//CHECK-EMPTY: +//CHECK-NEXT: store2: +//CHECK-NEXT: %2 = getelementptr i256, i256* %0, i32 12 +//CHECK-NEXT: store i256 0, i256* %2, align 4 +//CHECK-NEXT: br label %unrolled_loop3 +//CHECK-EMPTY: +//CHECK-NEXT: unrolled_loop3: +//CHECK-NEXT: %3 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %4 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %5 = getelementptr [0 x i256], [0 x i256]* %4, i32 0, i256 0 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %3, [0 x i256]* null, i256* %5) +//CHECK-NEXT: %6 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %7 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %8 = getelementptr [0 x i256], [0 x i256]* %7, i32 0, i256 1 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %6, [0 x i256]* null, i256* %8) +//CHECK-NEXT: %9 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %10 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %11 = getelementptr [0 x i256], [0 x i256]* %10, i32 0, i256 2 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %9, [0 x i256]* null, i256* %11) +//CHECK-NEXT: %12 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %13 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %14 = getelementptr [0 x i256], [0 x i256]* %13, i32 0, i256 3 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %12, [0 x i256]* null, i256* %14) +//CHECK-NEXT: %15 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %16 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %17 = getelementptr [0 x i256], [0 x i256]* %16, i32 0, i256 4 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %15, [0 x i256]* null, i256* %17) +//CHECK-NEXT: br label %return4 +//CHECK-EMPTY: +//CHECK-NEXT: return4: +//CHECK-NEXT: %18 = getelementptr i256, i256* %0, i32 11 +//CHECK-NEXT: %19 = load i256, i256* %18, align 4 +//CHECK-NEXT: ret i256 %19 +//CHECK-NEXT: } +// +//CHECK-LABEL: define{{.*}} i256 @f_0.9(i256* %0){{.*}} { +//CHECK-NEXT: f_0.9: +//CHECK-NEXT: br label %store1 +//CHECK-EMPTY: +//CHECK-NEXT: store1: +//CHECK-NEXT: %1 = getelementptr i256, i256* %0, i32 11 +//CHECK-NEXT: store i256 0, i256* %1, align 4 +//CHECK-NEXT: br label %store2 +//CHECK-EMPTY: +//CHECK-NEXT: store2: +//CHECK-NEXT: %2 = getelementptr i256, i256* %0, i32 12 +//CHECK-NEXT: store i256 0, i256* %2, align 4 +//CHECK-NEXT: br label %unrolled_loop3 +//CHECK-EMPTY: +//CHECK-NEXT: unrolled_loop3: +//CHECK-NEXT: %3 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %4 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %5 = getelementptr [0 x i256], [0 x i256]* %4, i32 0, i256 0 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %3, [0 x i256]* null, i256* %5) +//CHECK-NEXT: %6 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %7 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %8 = getelementptr [0 x i256], [0 x i256]* %7, i32 0, i256 1 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %6, [0 x i256]* null, i256* %8) +//CHECK-NEXT: %9 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %10 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %11 = getelementptr [0 x i256], [0 x i256]* %10, i32 0, i256 2 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %9, [0 x i256]* null, i256* %11) +//CHECK-NEXT: %12 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %13 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %14 = getelementptr [0 x i256], [0 x i256]* %13, i32 0, i256 3 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %12, [0 x i256]* null, i256* %14) +//CHECK-NEXT: %15 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %16 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %17 = getelementptr [0 x i256], [0 x i256]* %16, i32 0, i256 4 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %15, [0 x i256]* null, i256* %17) +//CHECK-NEXT: %18 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %19 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %20 = getelementptr [0 x i256], [0 x i256]* %19, i32 0, i256 5 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %18, [0 x i256]* null, i256* %20) +//CHECK-NEXT: %21 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %22 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %23 = getelementptr [0 x i256], [0 x i256]* %22, i32 0, i256 6 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %21, [0 x i256]* null, i256* %23) +//CHECK-NEXT: %24 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %25 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %26 = getelementptr [0 x i256], [0 x i256]* %25, i32 0, i256 7 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %24, [0 x i256]* null, i256* %26) +//CHECK-NEXT: %27 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %28 = bitcast i256* %0 to [0 x i256]* +//CHECK-NEXT: %29 = getelementptr [0 x i256], [0 x i256]* %28, i32 0, i256 8 +//CHECK-NEXT: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %27, [0 x i256]* null, i256* %29) +//CHECK-NEXT: br label %return4 +//CHECK-EMPTY: +//CHECK-NEXT: return4: +//CHECK-NEXT: %30 = getelementptr i256, i256* %0, i32 11 +//CHECK-NEXT: %31 = load i256, i256* %30, align 4 +//CHECK-NEXT: ret i256 %31 +//CHECK-NEXT: } +// +//CHECK-LABEL: define{{.*}} void @MultiUse_0_run([0 x i256]* %0){{.*}} { +//CHECK-NEXT: prelude: +//CHECK-NEXT: %lvars = alloca [0 x i256], align 8 +//CHECK-NEXT: %subcmps = alloca [0 x { [0 x i256]*, i32 }], align 8 +//CHECK-NEXT: br label %call1 +//CHECK-EMPTY: +//CHECK-NEXT: call1: +//CHECK-NEXT: %f_0.2_arena = alloca [13 x i256], align 8 +//CHECK-NEXT: %1 = getelementptr [13 x i256], [13 x i256]* %f_0.2_arena, i32 0, i32 0 +//CHECK-NEXT: %2 = getelementptr [0 x i256], [0 x i256]* %0, i32 0, i32 3 +//CHECK-NEXT: %copy_src_0 = getelementptr i256, i256* %2, i32 0 +//CHECK-NEXT: %copy_dst_0 = getelementptr i256, i256* %1, i32 0 +//CHECK-NEXT: %copy_val_0 = load i256, i256* %copy_src_0, align 4 +//CHECK-NEXT: store i256 %copy_val_0, i256* %copy_dst_0, align 4 +//CHECK-NEXT: %copy_src_1 = getelementptr i256, i256* %2, i32 1 +//CHECK-NEXT: %copy_dst_1 = getelementptr i256, i256* %1, i32 1 +//CHECK-NEXT: %copy_val_1 = load i256, i256* %copy_src_1, align 4 +//CHECK-NEXT: store i256 %copy_val_1, i256* %copy_dst_1, align 4 +//CHECK-NEXT: %copy_src_2 = getelementptr i256, i256* %2, i32 2 +//CHECK-NEXT: %copy_dst_2 = getelementptr i256, i256* %1, i32 2 +//CHECK-NEXT: %copy_val_2 = load i256, i256* %copy_src_2, align 4 +//CHECK-NEXT: store i256 %copy_val_2, i256* %copy_dst_2, align 4 +//CHECK-NEXT: %copy_src_3 = getelementptr i256, i256* %2, i32 3 +//CHECK-NEXT: %copy_dst_3 = getelementptr i256, i256* %1, i32 3 +//CHECK-NEXT: %copy_val_3 = load i256, i256* %copy_src_3, align 4 +//CHECK-NEXT: store i256 %copy_val_3, i256* %copy_dst_3, align 4 +//CHECK-NEXT: %copy_src_4 = getelementptr i256, i256* %2, i32 4 +//CHECK-NEXT: %copy_dst_4 = getelementptr i256, i256* %1, i32 4 +//CHECK-NEXT: %copy_val_4 = load i256, i256* %copy_src_4, align 4 +//CHECK-NEXT: store i256 %copy_val_4, i256* %copy_dst_4, align 4 +//CHECK-NEXT: %copy_src_5 = getelementptr i256, i256* %2, i32 5 +//CHECK-NEXT: %copy_dst_5 = getelementptr i256, i256* %1, i32 5 +//CHECK-NEXT: %copy_val_5 = load i256, i256* %copy_src_5, align 4 +//CHECK-NEXT: store i256 %copy_val_5, i256* %copy_dst_5, align 4 +//CHECK-NEXT: %copy_src_6 = getelementptr i256, i256* %2, i32 6 +//CHECK-NEXT: %copy_dst_6 = getelementptr i256, i256* %1, i32 6 +//CHECK-NEXT: %copy_val_6 = load i256, i256* %copy_src_6, align 4 +//CHECK-NEXT: store i256 %copy_val_6, i256* %copy_dst_6, align 4 +//CHECK-NEXT: %copy_src_7 = getelementptr i256, i256* %2, i32 7 +//CHECK-NEXT: %copy_dst_7 = getelementptr i256, i256* %1, i32 7 +//CHECK-NEXT: %copy_val_7 = load i256, i256* %copy_src_7, align 4 +//CHECK-NEXT: store i256 %copy_val_7, i256* %copy_dst_7, align 4 +//CHECK-NEXT: %copy_src_8 = getelementptr i256, i256* %2, i32 8 +//CHECK-NEXT: %copy_dst_8 = getelementptr i256, i256* %1, i32 8 +//CHECK-NEXT: %copy_val_8 = load i256, i256* %copy_src_8, align 4 +//CHECK-NEXT: store i256 %copy_val_8, i256* %copy_dst_8, align 4 +//CHECK-NEXT: %copy_src_9 = getelementptr i256, i256* %2, i32 9 +//CHECK-NEXT: %copy_dst_9 = getelementptr i256, i256* %1, i32 9 +//CHECK-NEXT: %copy_val_9 = load i256, i256* %copy_src_9, align 4 +//CHECK-NEXT: store i256 %copy_val_9, i256* %copy_dst_9, align 4 +//CHECK-NEXT: %3 = getelementptr [13 x i256], [13 x i256]* %f_0.2_arena, i32 0, i32 10 +//CHECK-NEXT: store i256 2, i256* %3, align 4 +//CHECK-NEXT: %4 = bitcast [13 x i256]* %f_0.2_arena to i256* +//CHECK-NEXT: %call.f_0.2 = call i256 @f_0.2(i256* %4) +//CHECK-NEXT: %5 = getelementptr [0 x i256], [0 x i256]* %0, i32 0, i32 0 +//CHECK-NEXT: store i256 %call.f_0.2, i256* %5, align 4 +//CHECK-NEXT: br label %call2 +//CHECK-EMPTY: +//CHECK-NEXT: call2: +//CHECK-NEXT: %f_0.5_arena = alloca [13 x i256], align 8 +//CHECK-NEXT: %6 = getelementptr [13 x i256], [13 x i256]* %f_0.5_arena, i32 0, i32 0 +//CHECK-NEXT: %7 = getelementptr [0 x i256], [0 x i256]* %0, i32 0, i32 3 +//CHECK-NEXT: %copy_src_01 = getelementptr i256, i256* %7, i32 0 +//CHECK-NEXT: %copy_dst_02 = getelementptr i256, i256* %6, i32 0 +//CHECK-NEXT: %copy_val_03 = load i256, i256* %copy_src_01, align 4 +//CHECK-NEXT: store i256 %copy_val_03, i256* %copy_dst_02, align 4 +//CHECK-NEXT: %copy_src_14 = getelementptr i256, i256* %7, i32 1 +//CHECK-NEXT: %copy_dst_15 = getelementptr i256, i256* %6, i32 1 +//CHECK-NEXT: %copy_val_16 = load i256, i256* %copy_src_14, align 4 +//CHECK-NEXT: store i256 %copy_val_16, i256* %copy_dst_15, align 4 +//CHECK-NEXT: %copy_src_27 = getelementptr i256, i256* %7, i32 2 +//CHECK-NEXT: %copy_dst_28 = getelementptr i256, i256* %6, i32 2 +//CHECK-NEXT: %copy_val_29 = load i256, i256* %copy_src_27, align 4 +//CHECK-NEXT: store i256 %copy_val_29, i256* %copy_dst_28, align 4 +//CHECK-NEXT: %copy_src_310 = getelementptr i256, i256* %7, i32 3 +//CHECK-NEXT: %copy_dst_311 = getelementptr i256, i256* %6, i32 3 +//CHECK-NEXT: %copy_val_312 = load i256, i256* %copy_src_310, align 4 +//CHECK-NEXT: store i256 %copy_val_312, i256* %copy_dst_311, align 4 +//CHECK-NEXT: %copy_src_413 = getelementptr i256, i256* %7, i32 4 +//CHECK-NEXT: %copy_dst_414 = getelementptr i256, i256* %6, i32 4 +//CHECK-NEXT: %copy_val_415 = load i256, i256* %copy_src_413, align 4 +//CHECK-NEXT: store i256 %copy_val_415, i256* %copy_dst_414, align 4 +//CHECK-NEXT: %copy_src_516 = getelementptr i256, i256* %7, i32 5 +//CHECK-NEXT: %copy_dst_517 = getelementptr i256, i256* %6, i32 5 +//CHECK-NEXT: %copy_val_518 = load i256, i256* %copy_src_516, align 4 +//CHECK-NEXT: store i256 %copy_val_518, i256* %copy_dst_517, align 4 +//CHECK-NEXT: %copy_src_619 = getelementptr i256, i256* %7, i32 6 +//CHECK-NEXT: %copy_dst_620 = getelementptr i256, i256* %6, i32 6 +//CHECK-NEXT: %copy_val_621 = load i256, i256* %copy_src_619, align 4 +//CHECK-NEXT: store i256 %copy_val_621, i256* %copy_dst_620, align 4 +//CHECK-NEXT: %copy_src_722 = getelementptr i256, i256* %7, i32 7 +//CHECK-NEXT: %copy_dst_723 = getelementptr i256, i256* %6, i32 7 +//CHECK-NEXT: %copy_val_724 = load i256, i256* %copy_src_722, align 4 +//CHECK-NEXT: store i256 %copy_val_724, i256* %copy_dst_723, align 4 +//CHECK-NEXT: %copy_src_825 = getelementptr i256, i256* %7, i32 8 +//CHECK-NEXT: %copy_dst_826 = getelementptr i256, i256* %6, i32 8 +//CHECK-NEXT: %copy_val_827 = load i256, i256* %copy_src_825, align 4 +//CHECK-NEXT: store i256 %copy_val_827, i256* %copy_dst_826, align 4 +//CHECK-NEXT: %copy_src_928 = getelementptr i256, i256* %7, i32 9 +//CHECK-NEXT: %copy_dst_929 = getelementptr i256, i256* %6, i32 9 +//CHECK-NEXT: %copy_val_930 = load i256, i256* %copy_src_928, align 4 +//CHECK-NEXT: store i256 %copy_val_930, i256* %copy_dst_929, align 4 +//CHECK-NEXT: %8 = getelementptr [13 x i256], [13 x i256]* %f_0.5_arena, i32 0, i32 10 +//CHECK-NEXT: store i256 5, i256* %8, align 4 +//CHECK-NEXT: %9 = bitcast [13 x i256]* %f_0.5_arena to i256* +//CHECK-NEXT: %call.f_0.5 = call i256 @f_0.5(i256* %9) +//CHECK-NEXT: %10 = getelementptr [0 x i256], [0 x i256]* %0, i32 0, i32 1 +//CHECK-NEXT: store i256 %call.f_0.5, i256* %10, align 4 +//CHECK-NEXT: br label %call3 +//CHECK-EMPTY: +//CHECK-NEXT: call3: +//CHECK-NEXT: %f_0.9_arena = alloca [13 x i256], align 8 +//CHECK-NEXT: %11 = getelementptr [13 x i256], [13 x i256]* %f_0.9_arena, i32 0, i32 0 +//CHECK-NEXT: %12 = getelementptr [0 x i256], [0 x i256]* %0, i32 0, i32 3 +//CHECK-NEXT: %copy_src_031 = getelementptr i256, i256* %12, i32 0 +//CHECK-NEXT: %copy_dst_032 = getelementptr i256, i256* %11, i32 0 +//CHECK-NEXT: %copy_val_033 = load i256, i256* %copy_src_031, align 4 +//CHECK-NEXT: store i256 %copy_val_033, i256* %copy_dst_032, align 4 +//CHECK-NEXT: %copy_src_134 = getelementptr i256, i256* %12, i32 1 +//CHECK-NEXT: %copy_dst_135 = getelementptr i256, i256* %11, i32 1 +//CHECK-NEXT: %copy_val_136 = load i256, i256* %copy_src_134, align 4 +//CHECK-NEXT: store i256 %copy_val_136, i256* %copy_dst_135, align 4 +//CHECK-NEXT: %copy_src_237 = getelementptr i256, i256* %12, i32 2 +//CHECK-NEXT: %copy_dst_238 = getelementptr i256, i256* %11, i32 2 +//CHECK-NEXT: %copy_val_239 = load i256, i256* %copy_src_237, align 4 +//CHECK-NEXT: store i256 %copy_val_239, i256* %copy_dst_238, align 4 +//CHECK-NEXT: %copy_src_340 = getelementptr i256, i256* %12, i32 3 +//CHECK-NEXT: %copy_dst_341 = getelementptr i256, i256* %11, i32 3 +//CHECK-NEXT: %copy_val_342 = load i256, i256* %copy_src_340, align 4 +//CHECK-NEXT: store i256 %copy_val_342, i256* %copy_dst_341, align 4 +//CHECK-NEXT: %copy_src_443 = getelementptr i256, i256* %12, i32 4 +//CHECK-NEXT: %copy_dst_444 = getelementptr i256, i256* %11, i32 4 +//CHECK-NEXT: %copy_val_445 = load i256, i256* %copy_src_443, align 4 +//CHECK-NEXT: store i256 %copy_val_445, i256* %copy_dst_444, align 4 +//CHECK-NEXT: %copy_src_546 = getelementptr i256, i256* %12, i32 5 +//CHECK-NEXT: %copy_dst_547 = getelementptr i256, i256* %11, i32 5 +//CHECK-NEXT: %copy_val_548 = load i256, i256* %copy_src_546, align 4 +//CHECK-NEXT: store i256 %copy_val_548, i256* %copy_dst_547, align 4 +//CHECK-NEXT: %copy_src_649 = getelementptr i256, i256* %12, i32 6 +//CHECK-NEXT: %copy_dst_650 = getelementptr i256, i256* %11, i32 6 +//CHECK-NEXT: %copy_val_651 = load i256, i256* %copy_src_649, align 4 +//CHECK-NEXT: store i256 %copy_val_651, i256* %copy_dst_650, align 4 +//CHECK-NEXT: %copy_src_752 = getelementptr i256, i256* %12, i32 7 +//CHECK-NEXT: %copy_dst_753 = getelementptr i256, i256* %11, i32 7 +//CHECK-NEXT: %copy_val_754 = load i256, i256* %copy_src_752, align 4 +//CHECK-NEXT: store i256 %copy_val_754, i256* %copy_dst_753, align 4 +//CHECK-NEXT: %copy_src_855 = getelementptr i256, i256* %12, i32 8 +//CHECK-NEXT: %copy_dst_856 = getelementptr i256, i256* %11, i32 8 +//CHECK-NEXT: %copy_val_857 = load i256, i256* %copy_src_855, align 4 +//CHECK-NEXT: store i256 %copy_val_857, i256* %copy_dst_856, align 4 +//CHECK-NEXT: %copy_src_958 = getelementptr i256, i256* %12, i32 9 +//CHECK-NEXT: %copy_dst_959 = getelementptr i256, i256* %11, i32 9 +//CHECK-NEXT: %copy_val_960 = load i256, i256* %copy_src_958, align 4 +//CHECK-NEXT: store i256 %copy_val_960, i256* %copy_dst_959, align 4 +//CHECK-NEXT: %13 = getelementptr [13 x i256], [13 x i256]* %f_0.9_arena, i32 0, i32 10 +//CHECK-NEXT: store i256 9, i256* %13, align 4 +//CHECK-NEXT: %14 = bitcast [13 x i256]* %f_0.9_arena to i256* +//CHECK-NEXT: %call.f_0.9 = call i256 @f_0.9(i256* %14) +//CHECK-NEXT: %15 = getelementptr [0 x i256], [0 x i256]* %0, i32 0, i32 2 +//CHECK-NEXT: store i256 %call.f_0.9, i256* %15, align 4 +//CHECK-NEXT: br label %prologue +//CHECK-EMPTY: +//CHECK-NEXT: prologue: +//CHECK-NEXT: ret void +//CHECK-NEXT: } diff --git a/circom/tests/type_conversions/bool_4.circom b/circom/tests/type_conversions/bool_4.circom index 34da8f1aa..dbb2d4d02 100644 --- a/circom/tests/type_conversions/bool_4.circom +++ b/circom/tests/type_conversions/bool_4.circom @@ -27,7 +27,7 @@ component main = A(); //CHECK: store i256 %[[VAL]], i256* %{{[0-9]+}} //CHECK-LABEL: define{{.*}} i256* @binop_bool_array_ -//CHECK-SAME: [[$F_ID_2:[0-9]+]](i256* %0){{.*}} { +//CHECK-SAME: [[$F_ID_2:[0-9\.]+]](i256* %0){{.*}} { //CHECK-COUNT-10: call void @..generated..loop.body.[[$F_ID_1]]([0 x i256]* %{{[0-9]+}}, [0 x i256]* null, i256* %{{[0-9]+}}, i256* %{{[0-9]+}}, i256* %{{[0-9]+}}) //CHECK-LABEL: define{{.*}} void @A_{{[0-9]+}}_run([0 x i256]* %0){{.*}} { diff --git a/circuit_passes/src/bucket_interpreter/env/unrolled_block_env.rs b/circuit_passes/src/bucket_interpreter/env/unrolled_block_env.rs index 9f4c7acff..7a61a32da 100644 --- a/circuit_passes/src/bucket_interpreter/env/unrolled_block_env.rs +++ b/circuit_passes/src/bucket_interpreter/env/unrolled_block_env.rs @@ -59,7 +59,7 @@ impl<'a> UnrolledBlockEnvData<'a> { } pub fn function_caller(&self) -> Option<&BucketId> { - None + self.base.function_caller() } pub fn get_context_kind(&self) -> EnvContextKind { diff --git a/circuit_passes/src/passes/loop_unroll/mod.rs b/circuit_passes/src/passes/loop_unroll/mod.rs index 06759dbb7..133e9bbf2 100644 --- a/circuit_passes/src/passes/loop_unroll/mod.rs +++ b/circuit_passes/src/passes/loop_unroll/mod.rs @@ -3,16 +3,18 @@ mod extracted_location_updater; pub mod body_extractor; use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::vec; use code_producers::llvm_elements::stdlib::GENERATED_FN_PREFIX; +use compiler::circuit_design::function::FunctionCode; use compiler::circuit_design::template::TemplateCode; use compiler::compiler_interface::Circuit; use compiler::intermediate_representation::{ new_id, BucketId, InstructionList, InstructionPointer, ToSExp, UpdateId, }; use compiler::intermediate_representation::ir_interface::*; -use crate::bucket_interpreter::env::Env; +use indexmap::{IndexMap, IndexSet}; +use crate::bucket_interpreter::env::{Env, LibraryAccess}; use crate::bucket_interpreter::error::{self, BadInterp}; use crate::bucket_interpreter::memory::PassMemory; use crate::bucket_interpreter::observer::Observer; @@ -27,12 +29,33 @@ const DEBUG_LOOP_UNROLL: bool = false; pub const LOOP_BODY_FN_PREFIX: &str = const_format::concatcp!(GENERATED_FN_PREFIX, "loop.body."); +/// Maps LoopBucket id to the unrolled version. +// Uses BTreeMap instead of HashMap because this type must implement the Hash trait. +type UnrolledLoops = BTreeMap; +type UnrolledLoopCounts = BTreeMap; + pub struct LoopUnrollPass<'d> { global_data: &'d RefCell, memory: PassMemory, extractor: LoopBodyExtractor, // Wrapped in a RefCell because the reference to the static analysis is immutable but we need mutability - replacements: RefCell>, + // + /// Track the order that the loops appear during the traversal to stabilize the order + /// they appear in the new function names. + loop_bucket_order: RefCell>, + /// Maps the ID of the CallBucket that is currently on the interpreter's stack (or None + /// if the interpreter is currently analyzing code that is not within a function) to a + /// mapping of LoopBucket id to its unrolled replacement body. + replacements: RefCell, UnrolledLoops>>, + /// Maps CallBucket symbol (i.e. target function name) plus a mapping of LoopBucket IDs to + /// iteration counts to the new function that has loops unrolled according to that mapping. + /// Uses IndexMap to ensure consistent ordering of functions in the output (for lit tests). + new_functions: RefCell>>, + /// Within the CircuitTransformationPass impl below, this holds the unrolled loop bodies for + /// when the function is called by the current CallBucket. The None key in this map is for the + /// cases that are NOT inside a function. When traversal enters a function, this will change to + /// the UnrolledLoops for that CallBucket. + caller_context: RefCell>, } impl<'d> LoopUnrollPass<'d> { @@ -40,8 +63,11 @@ impl<'d> LoopUnrollPass<'d> { LoopUnrollPass { global_data, memory: PassMemory::new(prime), - replacements: Default::default(), extractor: Default::default(), + loop_bucket_order: Default::default(), + replacements: Default::default(), + new_functions: Default::default(), + caller_context: Default::default(), } } @@ -51,14 +77,14 @@ impl<'d> LoopUnrollPass<'d> { env: &Env, ) -> Result<(Option, usize), BadInterp> { if DEBUG_LOOP_UNROLL { - println!("\nTry unrolling loop {}:", bucket.id); + println!("\n[UNROLL] Try unrolling loop {}:", bucket.id); for (i, s) in bucket.body.iter().enumerate() { println!("[{}/{}]{}", i + 1, bucket.body.len(), s.to_sexp().to_pretty(100)); } for (i, s) in bucket.body.iter().enumerate() { println!("[{}/{}]{:?}", i + 1, bucket.body.len(), s); } - println!("LOOP ENTRY env {}", env); + println!("[UNROLL] LOOP ENTRY env {}", env); } // Compute loop iteration count. If unknown, return immediately. let recorder = EnvRecorder::new(self.global_data, &self.memory); @@ -78,7 +104,7 @@ impl<'d> LoopUnrollPass<'d> { interpreter.execute_loop_bucket_once(bucket, inner_env, true)?; if DEBUG_LOOP_UNROLL { println!( - "[try_unroll_loop] execute_loop_bucket_once -> cond={:?}, env={:?}", + "[UNROLL][try_unroll_loop] execute_loop_bucket_once() -> cond={:?}, env={:?}", cond, new_env ); } @@ -95,7 +121,7 @@ impl<'d> LoopUnrollPass<'d> { recorder.drop_header_env(); //free Env from the final iteration } if DEBUG_LOOP_UNROLL { - println!("recorder = {:?}", recorder); + println!("[UNROLL] recorder = {:?}", recorder); } let num_iter = recorder.get_iter(); @@ -106,6 +132,11 @@ impl<'d> LoopUnrollPass<'d> { // Otherwise, just duplicate the body 'num_iter' number of times. match &bucket.body[..] { [a] => { + if DEBUG_LOOP_UNROLL { + println!( + "[UNROLL][try_unroll_loop] OUTCOME: safe to move, single statement, in-place" + ); + } for _ in 0..num_iter { let mut copy = a.clone(); copy.update_id(); @@ -113,6 +144,9 @@ impl<'d> LoopUnrollPass<'d> { } } _ => { + if DEBUG_LOOP_UNROLL { + println!("[UNROLL][try_unroll_loop] OUTCOME: safe to move, extracting"); + } self.extractor.extract( bucket, recorder, @@ -123,6 +157,9 @@ impl<'d> LoopUnrollPass<'d> { } } else { //If the loop body is not safe to move into a new function, just unroll in-place. + if DEBUG_LOOP_UNROLL { + println!("[UNROLL][try_unroll_loop] OUTCOME: not safe to move, unrolling in-place"); + } for _ in 0..num_iter { for s in &bucket.body { let mut copy = s.clone(); @@ -134,9 +171,11 @@ impl<'d> LoopUnrollPass<'d> { Ok((Some(block_body), num_iter)) } - // Will take the unrolled loop and interpretate it - // checking if new loop buckets appear + // Will interpret the unrolled loop to check for additional loops inside fn continue_inside(&self, bucket: &BlockBucket, env: &Env) -> Result<(), BadInterp> { + if DEBUG_LOOP_UNROLL { + println!("[UNROLL][continue_inside] with {}", env); + } let interpreter = self.memory.build_interpreter(self.global_data, self); let env = Env::new_unroll_block_env(env.clone(), &self.extractor); interpreter.execute_block_bucket(bucket, env, true)?; @@ -148,8 +187,12 @@ impl Observer> for LoopUnrollPass<'_> { fn on_loop_bucket(&self, bucket: &LoopBucket, env: &Env) -> Result { let result = self.try_unroll_loop(bucket, env); if DEBUG_LOOP_UNROLL { - println!("[try_unroll_loop] result = {:?}", result); + println!("[UNROLL][try_unroll_loop] result = {:?}", result); } + // Add the loop bucket to the ordering for the before visiting within via continue_inside() + // so that outer loop iteration counts appear first in the new function name + self.loop_bucket_order.borrow_mut().insert(bucket.id); + // if let (Some(block_body), n_iters) = result? { let block = BlockBucket { id: new_id(), @@ -161,8 +204,25 @@ impl Observer> for LoopUnrollPass<'_> { label: String::from("unrolled_loop"), }; self.continue_inside(&block, env)?; - self.replacements.borrow_mut().insert(bucket.id, block); + + let caller_id = env.function_caller().cloned(); + if DEBUG_LOOP_UNROLL { + println!( + "[UNROLL][on_loop_bucket] storing replacement for {} from caller {:?} :: {:?}", + bucket.id, caller_id, block + ); + } + // NOTE: 'caller_id' is None when the current loop is NOT located within a function. + let previously_added = self + .replacements + .borrow_mut() + .entry(caller_id) + .or_default() + .insert(bucket.id, block); + assert!(previously_added.is_none(), "Overwriting {:?}", previously_added); } + // Do not continue observing within this loop bucket because continue_inside() + // runs a new interpreter inside the unrolled body that is observed instead. Ok(false) } @@ -181,25 +241,124 @@ impl CircuitTransformationPass for LoopUnrollPass<'_> { default__run_template!(); fn post_hook_circuit(&self, cir: &mut Circuit) -> Result<(), BadInterp> { - // Transform and add the new body functions + // Transform and add the new body functions from the extractor let new_funcs = self.extractor.get_new_functions(); cir.functions.reserve_exact(new_funcs.len()); for f in new_funcs.iter() { cir.functions.insert(0, self.transform_function(&f)?); } - //ASSERT: All unrolled replacements were applied - assert!(self.replacements.borrow().is_empty()); + // Add the duplicated versions of functions created by transform_call_bucket() + for (_, ev) in self.new_functions.borrow_mut().drain(..) { + for f in ev.into_values() { + cir.functions.push(f); + } + } + //ASSERT: All call buckets were visited and updated (only the None key may remain) + assert!(self.replacements.borrow().iter().all(|(k, _)| k.is_none())); Ok(()) } + fn transform_call_bucket(&self, bucket: &CallBucket) -> Result { + if DEBUG_LOOP_UNROLL { + println!("[UNROLL][transform_call_bucket] {:?}", bucket); + } + let call_bucket_id = Some(bucket.id); + // The Some keys in the 'replacements' map are for the cases that are + // inside a function when executed from the CallBucket.id used as the key. + // NOTE: This borrow is inside brackets to prevent runtime double borrow error. + let reps = { self.replacements.borrow_mut().remove(&call_bucket_id) }; + if let Some(loop_replacements) = reps { + assert!(!loop_replacements.is_empty()); + + // Check if the needed function exists, else create it. + let new_target = { + let old_target = &bucket.symbol; + let versions_key: UnrolledLoopCounts = + loop_replacements.iter().map(|(k, v)| (*k, v.n_iters)).collect(); + // Must not use a mutable borrow on `self.new_functions` here because the `transform_function` + // call below can recurse back to here and result in BorrowMutError. + let cached_new_target = self + .new_functions + .borrow() + .get(old_target) + .and_then(|m| m.get(&versions_key)) + .map(|f| f.header.clone()); + if let Some(new_target) = cached_new_target { + new_target + } else { + // Set the caller context and then use self.transform_function(..) on the existing + // function to create a new FunctionCode by running this transformer on the existing one. + let old_ctx = self.caller_context.replace(Some(loop_replacements)); + if DEBUG_LOOP_UNROLL { + println!( + "[UNROLL][transform_call_bucket] set caller_context = {:?}", + self.caller_context.borrow() + ); + } + let mut res = self.transform_function(&self.memory.get_function(old_target))?; + self.caller_context.replace(old_ctx); + if DEBUG_LOOP_UNROLL { + println!( + "[UNROLL][transform_call_bucket] restored caller_context = {:?}", + self.caller_context.borrow() + ); + } + // Build the new function name according to the condition values but sorted by 'loop_bucket_order' + let new_name = self + .loop_bucket_order + .borrow() + .iter() + .filter_map(|id| versions_key.get(id)) + .fold(old_target.clone(), |acc, c| format!("{}.{}", acc, c)); + res.header = new_name.clone(); + if DEBUG_LOOP_UNROLL { + println!("[UNROLL][transform_call_bucket] created function {:?}", res); + } + // Store the new function + let previously_added = self + .new_functions + .borrow_mut() + .entry(old_target.clone()) + .or_default() + .insert(versions_key, res); + assert!(previously_added.is_none(), "Overwriting {:?}", previously_added); + new_name + } + }; + if DEBUG_LOOP_UNROLL { + println!( + "[UNROLL][transform_call_bucket] replace call to {} with {}", + bucket.symbol, new_target + ); + } + return Ok(CallBucket { + id: new_id(), + source_file_id: bucket.source_file_id, + line: bucket.line, + message_id: bucket.message_id, + symbol: new_target, + argument_types: bucket.argument_types.clone(), + arguments: self.transform_instructions_fixed_len(&bucket.arguments)?, + arena_size: bucket.arena_size, + return_info: self.transform_return_type(&bucket.id, &bucket.return_info)?, + } + .allocate()); + } + self.transform_call_bucket_default(bucket) + } + fn transform_loop_bucket(&self, bucket: &LoopBucket) -> Result { - //NOTE: The bracket and assignment are needed here so the mutable borrow goes out of scope before the - // transform* function is called within the match expression to avoid "already borrowed: BorrowMutError" - let rep = { self.replacements.borrow_mut().remove(&bucket.id) }; - match rep { - Some(unrolled_loop) => self.transform_block_bucket(&unrolled_loop), - None => self.transform_loop_bucket_default(bucket), + if DEBUG_LOOP_UNROLL { + println!("[UNROLL][transform_loop_bucket] {:?}", bucket); + } + // Get from the current 'caller_context' or lookup via None key in 'evaluated_conditions' + let reps = self.replacements.borrow(); + if let Some(m) = self.caller_context.borrow().as_ref().or_else(|| reps.get(&None)) { + if let Some(unrolled) = m.get(&bucket.id) { + return self.transform_block_bucket(unrolled); + } } + self.transform_loop_bucket_default(bucket) } }