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

feat: simplify jmpifs by reversing branches if condition is negated #5891

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

Conversation

TomAFrench
Copy link
Member

Description

Problem*

Resolves

Summary*

Currently if an if-else block has a negated condition we generate an extra instruction to calculate the condition rather than just swapping the "if" and "else" branches. I've added a check for this in the simplify_cfg pass.

Additional Context

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.

Copy link
Contributor

github-actions bot commented Sep 3, 2024

Changes to Brillig bytecode sizes

Generated at commit: f10a18b98403ebc81ad2365f1704f862b9397f35, compared to commit: 7d612425728a9d5852ec26a96af629aec7cd0a21

🧾 Summary (10% most significant diffs)

Program Brillig opcodes (+/-) %
reference_counts -4 ✅ -1.16%
acir_inside_brillig_recursion -1 ✅ -1.22%
fold_fibonacci -1 ✅ -1.30%
brillig_recursion -1 ✅ -1.30%
references -4 ✅ -1.97%

Full diff report 👇
Program Brillig opcodes (+/-) %
debug_logs 5,007 (-1) -0.02%
regression_5252 4,653 (-1) -0.02%
poseidonsponge_x5_254 4,268 (-1) -0.02%
u128 2,795 (-1) -0.04%
nested_array_dynamic 1,991 (-1) -0.05%
sha256_brillig_performance_regression 3,239 (-2) -0.06%
uhashmap 13,518 (-10) -0.07%
nested_array_in_slice 1,199 (-1) -0.08%
brillig_cow_regression 2,158 (-2) -0.09%
sha2_byte 2,811 (-3) -0.11%
ecdsa_secp256k1 902 (-1) -0.11%
slice_dynamic_index 2,637 (-3) -0.11%
array_dynamic_nested_blackbox_input 871 (-1) -0.11%
eddsa 10,673 (-14) -0.13%
regression_4449 728 (-1) -0.14%
conditional_regression_short_circuit 1,200 (-2) -0.17%
conditional_1 1,192 (-2) -0.17%
6 1,127 (-2) -0.18%
sha256_regression 6,556 (-12) -0.18%
array_dynamic_blackbox_input 1,025 (-2) -0.19%
aes128_encrypt 512 (-1) -0.19%
ram_blowup_regression 958 (-2) -0.21%
sha256 2,233 (-5) -0.22%
sha256_var_size_regression 1,713 (-4) -0.23%
sha256_var_witness_const_regression 1,241 (-3) -0.24%
sha256_var_padding_regression 4,781 (-12) -0.25%
hashmap 20,468 (-57) -0.28%
array_sort 303 (-1) -0.33%
slices 2,029 (-8) -0.39%
bigint 1,986 (-10) -0.50%
slice_regex 2,152 (-14) -0.65%
reference_counts 340 (-4) -1.16%
acir_inside_brillig_recursion 81 (-1) -1.22%
fold_fibonacci 76 (-1) -1.30%
brillig_recursion 76 (-1) -1.30%
references 199 (-4) -1.97%

@TomAFrench
Copy link
Member Author

I'm really unsure on why exactly this is causing test failures. The ordering of the branches in a jmpif shouldn't cause any changes to behaviour afaik.

@TomAFrench
Copy link
Member Author

This branch changes the order of the jmpif as expected in the simplify_cfg pass however the issue comes up in flattening as we end up merging the values incorrectly.

Master

acir(inline) fn main f0 {
  b0(v0: Field):
    v1 = allocate
    v2 = allocate
    store Field 0 at v2
    v5 = eq v0, Field 10
    v6 = not v5
    enable_side_effects v6
    v7 = load v2
    store Field 2 at v2
    store v7 at v2
    enable_side_effects u1 1
    v10 = cast v6 as Field
    v11 = cast v5 as Field
    v12 = mul v10, Field 2 // NOT(v5) * 2
    v13 = mul v11, v7 // v5 * 0
    v14 = add v12, v13
    store v14 at v2
    v15 = load v2
    v16 = eq v15, Field 2
    constrain v15 == Field 2
    return 
}

This branch

acir(inline) fn main f0 {
  b0(v0: Field):
    v1 = allocate
    v2 = allocate
    store Field 0 at v2
    v5 = eq v0, Field 10
    v6 = not v5
    enable_side_effects v5
    v7 = load v2
    store Field 2 at v2
    v9 = not v5 
    store v7 at v2
    enable_side_effects u1 1
    v11 = cast v5 as Field
    v12 = cast v9 as Field
    v13 = mul v11, Field 2 // v5 * 2
    v14 = mul v12, v7 // NOT(v5) * 0
    v15 = add v13, v14
    store v15 at v2
    v16 = load v2
    v17 = eq v16, Field 2
    constrain v16 == Field 2
    return 
}

@TomAFrench TomAFrench marked this pull request as draft September 4, 2024 12:52
@vezenovm
Copy link
Contributor

vezenovm commented Sep 4, 2024

This branch changes the order of the jmpif as expected in the simplify_cfg pass however the issue comes up in flattening as we end up merging the values incorrectly.

If we do not see an simple or obvious way to have this optimization work w/ flattening we could make this optimization just work on Brillig for now.

@jfecher
Copy link
Contributor

jfecher commented Sep 4, 2024

I'd like if we investigated why flattening isn't adapting to the new changes. Could be related to the bug I've been looking at.

@TomAFrench
Copy link
Member Author

Agreed, we should at least understand the root cause of this first.

TomAFrench and others added 6 commits September 10, 2024 13:42
* master: (60 commits)
  fix: suggest trait attributes in LSP (#5972)
  fix: Error when `quote` is used in runtime code (#5978)
  chore: document HashMap (#5984)
  fix: Restrict keccak256_injective test input to 8 bits (#5977)
  fix: Error when comptime functions are used in runtime code (#5976)
  chore: document BoundedVec (#5974)
  feat: add `Expr::as_let` (#5964)
  chore: remove 3 unused functions warnings in the stdlib (#5973)
  feat: let `nargo` and LSP work well in the stdlib (#5969)
  feat: show doc comments in LSP (#5968)
  feat: add a `panic` method to the stdlib (#5966)
  fix: LSP document symbol didn't work for primitive impls (#5970)
  fix(mem2reg): Handle aliases in function last store cleanup and additional alias unit test (#5967)
  fix: let `derive(Eq)` work for empty structs (#5965)
  feat: add `FunctionDefinition` methods `is_unconstrained` and `set_unconstrained` (#5962)
  feat: LSP autocompletion for attributes (#5963)
  feat: `Module::add_item` (#5947)
  feat: Add `StructDefinition::add_generic` (#5961)
  feat: Add `StructDefinition::name` (#5960)
  fix(mem2reg): Handle aliases better when setting a known value for a load (#5959)
  ...
@michaeljklein
Copy link
Contributor

michaeljklein commented Oct 22, 2024

I was able to narrow down one of the failing tests to the following:

This fails the final assertion:

// x == 5
fn main(x: Field) {
    let mut z = false;
    if x != 20 {
        z = true;
    }
    assert(x != 20);

    // fails here
    assert(z);
}

But this succeeds:

// x == 5
fn main(x: Field) {
    let mut z = false;
    if x != 20 {
        z = true;
    } else {
    }
    assert(x != 20);

    assert(z);
}

So there appears to be a bug around single-block if statements and a negated dynamic condition.

@michaeljklein
Copy link
Contributor

I was able to narrow the above case slightly to the following, which executes successfully on nargo nightly, but fails on this branch:

fn main(x: bool) {
    let mut z = false;
    if !x {
        z = true;
    }

    assert(!x);
    assert(z);
}
  • Adding an else {} still fails for this example
  • Using x in the if condition (with either x = true or x = false) succeeds

Dumping the SSA (skipping unused variables):

  • The simplification step still gives a correct SSA (one that would be expected to execute successfully)
// After Simplifying:
acir(inline) fn main f0 {
  // v0 = 0
  b0(v0: u1):
    v1 = allocate
    store u1 0 at v1
    // v1 = 0
    jmpif v0 then: b2, else: b1
  b2():
    // (skipped)
    store u1 0 at v1
    jmp b3()
  b3():
    constrain v0 == u1 0
    v6 = load v1
    // v6 = 1
    constrain v6 == u1 1
    return 
  b1():
    // branch taken
    store u1 1 at v1
    // v1 = 1
    jmp b3()
}

But flatting gives SSA that unconditionally fails (see final constrain):

// After Flattening:
acir(inline) fn main f0 {
  // v0 = 0
  b0(v0: u1):
    v1 = allocate
    store u1 0 at v1
    // v1 = 0
    enable_side_effects v0
    // no side effects
    v4 = load v1
    // v4 = 0
    store u1 1 at v1
    // not stored?
    v6 = not v0
    // v6 = 1
    store v4 at v1
    // not stored? either way, it's 0
    enable_side_effects v6
    store u1 0 at v1
    // v1 = 0
    enable_side_effects u1 1
    store v0 at v1
    // v1 = 0
    constrain v0 == u1 0
    v9 = load v1
    // v9 = 0
    constrain v9 == u1 1
    return 
}

Copy link
Contributor

github-actions bot commented Nov 25, 2024

Changes to number of Brillig opcodes executed

Generated at commit: f10a18b98403ebc81ad2365f1704f862b9397f35, compared to commit: 7d612425728a9d5852ec26a96af629aec7cd0a21

🧾 Summary (10% most significant diffs)

Program Brillig opcodes (+/-) %
acir_inside_brillig_recursion -8 ✅ -3.04%
brillig_recursion -176 ✅ -3.89%
fold_fibonacci -176 ✅ -3.89%
references -13 ✅ -4.26%

Full diff report 👇
Program Brillig opcodes (+/-) %
slices 3,290 (+4) +0.12%
slice_regex 3,399 (+1) +0.03%
regression_5252 959,126 (-4) -0.00%
sha2_byte 49,934 (-2) -0.00%
array_dynamic_blackbox_input 18,251 (-2) -0.01%
sha256_var_size_regression 16,354 (-2) -0.01%
slice_dynamic_index 4,680 (-1) -0.02%
sha256_brillig_performance_regression 26,366 (-8) -0.03%
brillig_cow_regression 518,937 (-166) -0.03%
ram_blowup_regression 778,429 (-256) -0.03%
conditional_1 5,727 (-2) -0.03%
debug_logs 5,019 (-2) -0.04%
sha256_var_padding_regression 219,651 (-92) -0.04%
array_dynamic_nested_blackbox_input 4,519 (-2) -0.04%
sha256_regression 116,146 (-58) -0.05%
uhashmap 148,002 (-142) -0.10%
array_sort 584 (-2) -0.34%
eddsa 736,072 (-2,546) -0.34%
u128 25,260 (-178) -0.70%
aes128_encrypt 4,451 (-32) -0.71%
hashmap 55,745 (-978) -1.72%
reference_counts 309 (-8) -2.52%
acir_inside_brillig_recursion 255 (-8) -3.04%
brillig_recursion 4,353 (-176) -3.89%
fold_fibonacci 4,353 (-176) -3.89%
references 292 (-13) -4.26%

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.

4 participants