Skip to content
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

fix: Fix poor handling of aliased references in flattening pass causing some values to be zeroed #6434

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from

Conversation

jfecher
Copy link
Contributor

@jfecher jfecher commented Nov 1, 2024

Description

Problem*

Resolves #5771

Summary*

Background

Fixes a bug which originates from merge_stores in the flattening pass which can in certain cases get confused with aliases and overwrite values incorrectly.

The rough steps to reproduce this issue are as follows, though I've been unsuccessful so far in reproducing it from scratch outside of the original branch in aztec packages.

  1. Create a mutable reference to something, for example an integer (context.side_effect_counter in the original bug)
  2. Use a runtime conditional if input_to_main { ... }, and ensure that input_to_main = false
  3. Within the conditional try to confuse the compiler by creating a mutable alias to the reference in a way that the alias only appears after the first mem2reg pass and before the flattening pass. In this case, using an immediately-invoked closure which captures the reference as part of its environment.
  4. Use some oracles or other functions which can't be inlined/evaluated currently within that closure.
  5. Create another nested if within the closure, also using a runtime condition
  6. Inside that if, conditionally mutate the captured reference.
  7. Back to the first if, after the closure is invoked, at the end of the if, mutate the original, non-captured reference.

This somewhat specific setup lead to a situation where the flattening pass saw it had a store to what it saw as two different references, but were really both aliases to the same reference. merge_stores uses a then_value, else_value, and old_value of each reference to create its new_value which it then stores back to the reference. One alias in this case had a correct then_value, while the other had a correct else_value, with the opposite value of both being equal to the old_value. Storing both of these in sequence lead to the final value for the else/false case to be !input_to_main * (input_to_main * else_value). Since one of input_to_main and its negation are always zero, the whole value ends up zeroed for the else case. Finally, confusion with aliases also leads the compiler to accept the else case value as the updated value even after the if terminates.

I've been unsuccessful trying to reproduce this from scratch so come Monday I'll work on copying over the same test case into an integration test here. I've already removed all macro code but need to figure out how to translate the oracle functions step mainly.

The Fix

Skipping some alternate fixes I tried, I figured out we could just remove merge_stores entirely if we never stored a non-conditional value to a reference to begin with during flattening. E.g. currently in flattening if we see if c { foo = 3; } we issue a store 3 in foo then after the if we go back and merge stores to issue a store (if c then 3 else prev_value) in foo afterward. I've changed it to instead issue prev_value = load foo; store (if c then 3 else prev_value) in foo to replace the original store. This means the value of a reference is always correct and we never have to fix it afterward. This greatly simplifies the flattening pass I think.

HOWEVER:

  • The first store to an allocation has no previous value: foo = allocate; store 3 in foo, so I had to add an initial_value: ValueId argument to the Allocate instruction.
    • Not requiring an initial store breaks the mem2reg pass... In particular the logic to remove the last store is breaking. For this reason I'll probably try to revert the Allocate argument change in favor of a hack to not insert a previous value load if allocate is the instruction previous to the current store since we always issue them one after another in practice.
  • Issuing an Instruction::IfElse for every store leads to more value merging in practice, resulting in increase constraint counts without something like feat: avoid branch condition negations in ValueMerger #6073.

Additional Context

Draft because I had to port this over from local changes within aztec packages (branch jf/fix-zeroed-bug) and there are a few merges that still need to be handled carefully - most notably the mem2reg changes.

Remaining work

  • Reproduce test
  • Revert Instruction::Allocate changes in favor of hack
  • Fix remaining merge diffs from this PR since it was ported from aztec-packages

Documentation*

Check one:

  • No documentation needed.
  • Documentation included in this PR.
  • [For Experimental Features] Documentation to be submitted in a separate PR.

PR Checklist*

  • I have tested the changes locally.
  • I have formatted the changes with Prettier and/or cargo fmt on default settings.

@jfecher
Copy link
Contributor Author

jfecher commented Nov 1, 2024

The size of this draft looks large but the actual changes are fairly small and it is mostly removed code. Most of the code size changes are due to merge diffs and the Instruction::Allocate change I'll revert. Fixed

Copy link
Contributor

github-actions bot commented Nov 4, 2024

Changes to Brillig bytecode sizes

Generated at commit: 4fc594d0a227fae216c49165622f90a47cf4fd5e, compared to commit: 53100647bf1dc7917b66c9a7041c06b1e716fbe7

🧾 Summary (10% most significant diffs)

Program Brillig opcodes (+/-) %
tuples +17 ❌ +36.96%
brillig_slices +184 ❌ +34.07%
closures_mut_ref +7 ❌ +30.43%
missing_closure_env +3 ❌ +13.64%
references +25 ❌ +8.56%
1_mul +5 ❌ +8.06%
derive +9 ❌ +6.62%

Full diff report 👇
Program Brillig opcodes (+/-) %
tuples 63 (+17) +36.96%
brillig_slices 724 (+184) +34.07%
closures_mut_ref 30 (+7) +30.43%
missing_closure_env 25 (+3) +13.64%
references 317 (+25) +8.56%
1_mul 67 (+5) +8.06%
derive 145 (+9) +6.62%
nested_array_in_slice 1,431 (+87) +6.47%
3_add 50 (+3) +6.38%
signed_arithmetic 271 (+16) +6.27%
fold_2_to_17 623 (+36) +6.13%
bench_2_to_17 355 (+18) +5.34%
poseidon2 362 (+18) +5.23%
integer_array_indexing 62 (+3) +5.08%
no_predicates_numeric_generic_poseidon 805 (+36) +4.68%
fold_numeric_generic_poseidon 805 (+36) +4.68%
hashmap 28,009 (+946) +3.50%
brillig_rc_regression_6123 179 (+6) +3.47%
regression 752 (+20) +2.73%
fold_complex_outputs 581 (+15) +2.65%
slices 2,364 (+60) +2.60%
uhashmap 16,940 (+414) +2.51%
slice_dynamic_index 2,933 (+64) +2.23%
eddsa 11,403 (+241) +2.16%
regression_capacity_tracker 237 (+4) +1.72%
brillig_cow 430 (+6) +1.42%
regression_struct_array_conditional 550 (+4) +0.73%
higher_order_functions 744 (+5) +0.68%
array_sort 298 (+2) +0.68%
sha2_byte 3,369 (+22) +0.66%
7_function 643 (+4) +0.63%
regression_5252 4,900 (+22) +0.45%
conditional_1 1,355 (+5) +0.37%
array_dynamic 329 (+1) +0.30%
brillig_cow_regression 2,421 (+6) +0.25%
6_array 439 (+1) +0.23%
keccak256 1,774 (+4) +0.23%
brillig_keccak 1,774 (+4) +0.23%
array_dynamic_blackbox_input 1,196 (+2) +0.17%
6 1,407 (+2) +0.14%
sha256 2,114 (+3) +0.14%
sha256_var_witness_const_regression 1,444 (+2) +0.14%
conditional_regression_short_circuit 1,477 (+2) +0.14%
brillig_sha256 816 (+1) +0.12%
regression_4449 879 (+1) +0.11%
sha256_var_padding_regression 5,380 (+6) +0.11%
array_dynamic_nested_blackbox_input 1,008 (+1) +0.10%
array_to_slice 1,020 (+1) +0.10%
ram_blowup_regression 1,031 (+1) +0.10%
ecdsa_secp256k1 1,033 (+1) +0.10%
sha256_var_size_regression 2,091 (+2) +0.10%
poseidonsponge_x5_254 4,504 (+4) +0.09%
sha256_regression 7,417 (+6) +0.08%
poseidon_bn254_hash 5,694 (+4) +0.07%
poseidon_bn254_hash_width_3 5,694 (+4) +0.07%
u128 2,918 (+1) +0.03%

Copy link
Contributor

github-actions bot commented Nov 4, 2024

Changes to circuit sizes

Generated at commit: 4fc594d0a227fae216c49165622f90a47cf4fd5e, compared to commit: 53100647bf1dc7917b66c9a7041c06b1e716fbe7

🧾 Summary (10% most significant diffs)

Program ACIR opcodes (+/-) % Circuit size (+/-) %
regression_5252 +97,584 ❌ +282.07% +130,183 ❌ +271.42%
conditional_1 +942 ❌ +22.68% +3,047 ❌ +25.55%
references +4 ❌ +66.67% +5 ❌ +23.81%
hashmap -22,567 ✅ -35.19% -31,202 ✅ -22.62%
debug_logs -6 ✅ -13.04% -54 ✅ -73.97%

Full diff report 👇
Program ACIR opcodes (+/-) % Circuit size (+/-) %
regression_5252 132,180 (+97,584) +282.07% 178,146 (+130,183) +271.42%
conditional_1 5,096 (+942) +22.68% 14,971 (+3,047) +25.55%
references 10 (+4) +66.67% 26 (+5) +23.81%
poseidonsponge_x5_254 1,334 (+294) +28.27% 1,431 (+176) +14.02%
slice_dynamic_index 1,252 (+270) +27.49% 6,902 (+449) +6.96%
array_sort 55 (+2) +3.77% 132 (+4) +3.13%
sha256_regression 20,423 (-14,667) -41.80% 200,572 (+3,112) +1.58%
sha256_var_size_regression 11,973 (-4,188) -25.91% 71,942 (+981) +1.38%
array_dynamic 106 (+4) +3.92% 3,728 (+14) +0.38%
side_effects_constrain_array 13 (+5) +62.50% 2,773 (+6) +0.22%
regression_capacity_tracker 117 (+4) +3.54% 3,972 (+8) +0.20%
binary_operator_overloading 284 (+2) +0.71% 4,465 (+2) +0.04%
bench_eddsa_poseidon 16,441 (+2) +0.01% 19,588 (+2) +0.01%
eddsa 64,631 (+2) +0.00% 65,817 (+2) +0.00%
conditional_regression_short_circuit 366 (-4) -1.08% 11,150 (-5) -0.04%
conditional_2 19 (-2) -9.52% 2,778 (-3) -0.11%
schnorr 1,513 (-6) -0.39% 23,906 (-26) -0.11%
regression_3607 37 (-2) -5.13% 2,798 (-4) -0.14%
conditional_regression_661 23 (-6) -20.69% 2,790 (-6) -0.21%
array_if_cond_simple 102 (-6) -5.56% 3,125 (-7) -0.22%
regression_mem_op_predicate 47 (-9) -16.07% 3,576 (-10) -0.28%
u128 654 (-26) -3.82% 4,664 (-25) -0.53%
slices 781 (-54) -6.47% 3,925 (-22) -0.56%
nested_array_in_slice 937 (+54) +6.12% 5,469 (-34) -0.62%
regression 116 (-25) -17.73% 3,654 (-29) -0.79%
bigint 1,085 (-81) -6.95% 8,043 (-74) -0.91%
sha256 1,569 (-309) -16.45% 20,917 (-390) -1.83%
hash_to_field 507 (-75) -12.89% 3,549 (-69) -1.91%
nested_array_dynamic 3,281 (+115) +3.63% 12,650 (-280) -2.17%
sha256_var_witness_const_regression 1,366 (-309) -18.45% 16,826 (-390) -2.27%
ram_blowup_regression 154,323 (-19,200) -11.06% 659,200 (-17,664) -2.61%
hashmap 41,567 (-22,567) -35.19% 106,736 (-31,202) -22.62%
debug_logs 40 (-6) -13.04% 19 (-54) -73.97%

Copy link
Contributor

github-actions bot commented Nov 4, 2024

Changes to number of Brillig opcodes executed

Generated at commit: 4fc594d0a227fae216c49165622f90a47cf4fd5e, compared to commit: 53100647bf1dc7917b66c9a7041c06b1e716fbe7

🧾 Summary (10% most significant diffs)

Program Brillig opcodes (+/-) %
tuples +17 ❌ +53.13%
closures_mut_ref +7 ❌ +36.84%
brillig_slices +197 ❌ +27.40%
missing_closure_env +3 ❌ +16.67%
1_mul +5 ❌ +10.00%
references +36 ❌ +8.55%
3_add +3 ❌ +8.11%

Full diff report 👇
Program Brillig opcodes (+/-) %
tuples 49 (+17) +53.13%
closures_mut_ref 26 (+7) +36.84%
brillig_slices 916 (+197) +27.40%
missing_closure_env 21 (+3) +16.67%
1_mul 55 (+5) +10.00%
references 457 (+36) +8.55%
3_add 40 (+3) +8.11%
signed_arithmetic 219 (+16) +7.88%
regression 2,909 (+209) +7.74%
slices 4,166 (+284) +7.32%
slice_dynamic_index 4,627 (+307) +7.11%
derive 142 (+9) +6.77%
nested_array_in_slice 1,618 (+87) +5.68%
integer_array_indexing 76 (+3) +4.11%
poseidon2 734 (+26) +3.67%
fold_numeric_generic_poseidon 5,435 (+175) +3.33%
no_predicates_numeric_generic_poseidon 5,435 (+175) +3.33%
brillig_rc_regression_6123 290 (+8) +2.84%
poseidon_bn254_hash 173,185 (+3,861) +2.28%
poseidon_bn254_hash_width_3 173,185 (+3,861) +2.28%
regression_5252 971,276 (+20,030) +2.11%
poseidonsponge_x5_254 194,877 (+3,960) +2.07%
fold_complex_outputs 871 (+15) +1.75%
hashmap 58,022 (+831) +1.45%
7_function 2,847 (+35) +1.24%
sha2_byte 47,764 (+561) +1.19%
regression_struct_array_conditional 1,303 (+12) +0.93%
eddsa 744,085 (+6,187) +0.84%
array_sort 506 (+4) +0.80%
uhashmap 153,617 (+1,133) +0.74%
regression_capacity_tracker 576 (+4) +0.70%
brillig_cow 1,288 (+6) +0.47%
array_dynamic_blackbox_input 19,453 (+66) +0.34%
higher_order_functions 1,523 (+5) +0.33%
array_dynamic 500 (+1) +0.20%
conditional_1 5,850 (+5) +0.09%
6_array 1,846 (+1) +0.05%
array_to_slice 2,359 (+1) +0.04%
fold_2_to_17 131,522,643 (+52,820) +0.04%
regression_4449 219,972 (+70) +0.03%
u128 26,165 (+8) +0.03%
sha256_var_witness_const_regression 7,043 (+2) +0.03%
sha256 10,861 (+3) +0.03%
6 7,377 (+2) +0.03%
conditional_regression_short_circuit 7,452 (+2) +0.03%
brillig_sha256 4,210 (+1) +0.02%
array_dynamic_nested_blackbox_input 4,509 (+1) +0.02%
bench_2_to_17 144,718,430 (+27,770) +0.02%
ecdsa_secp256k1 7,085 (+1) +0.01%
sha256_var_size_regression 18,356 (+2) +0.01%
brillig_keccak 37,071 (+4) +0.01%
keccak256 37,071 (+4) +0.01%
brillig_cow_regression 583,825 (+52) +0.01%
sha256_regression 126,359 (+6) +0.00%
sha256_var_padding_regression 234,947 (+6) +0.00%
ram_blowup_regression 877,663 (+1) +0.00%

Comment on lines +385 to +387
// if let Some(last_store) = references.last_stores.get(&address) {
// self.instructions_to_remove.insert(*last_store);
// }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to comment out this portion of mem2reg as it was causing the sha256 tests to fail. There seems to be an issue with tracking last_stores for some reason now.

Comment on lines 390 to 392
// Store instructions must be removed by DIE in acir code, any load
// instructions should already be unused by that point.
Store { .. } => matches!(function.runtime(), RuntimeType::Acir(_)),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the change to mem2reg, stores weren't being removed otherwise for some reason (even though the comment claims to just remove certain unneeded stores). So I had to change DIE to remove all stores here.

@jfecher
Copy link
Contributor Author

jfecher commented Nov 4, 2024

The brillig size increases are likely from the temporary store changes while mem2reg is fixed (should it be in this PR or separate?)
while the acir changes are likely due to the increased amount of value mergers which could possibly be helped by #6073.

jfecher and others added 4 commits November 5, 2024 10:02
…with-merger

* tf/experiment-with-alternative-merger:
  .
  feat: avoid branch condition negations in `ValueMerger`
@jfecher jfecher mentioned this pull request Nov 5, 2024
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Content of a struct gets "destroyed"
2 participants