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: try to inline brillig calls with all constant arguments #6466

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

Conversation

asterite
Copy link
Collaborator

@asterite asterite commented Nov 6, 2024

Description

Problem

Resolves #6453

Summary

Adds a pass that tries to inline brillig calls where all arguments are constant.

This is done by first creating a copy of the function, setting its runtime to ACIR, replacing its parameters by the call arguments, optimizing the function like we optimize everything else (except inlining no-predicates, because that requires all the functions to be in context: maybe this can be done as a follow-up PR but I'm not sure if it's worth it), then checking if the function was reduced to a single return terminator instruction.

Additional Context

The changes to the loop unrolling code are mainly there because now loop unrolling can happen inside brillig calls that we are trying to optimize out, so they can contain break and generally have a struct that can't happen in ACIR functions. I'm happy to try to find another way to do this, let me know!

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.

@asterite asterite changed the title Ab/inline const brillig calls feat: try to inline brillig calls with all constant arguments Nov 6, 2024
Copy link
Contributor

github-actions bot commented Nov 7, 2024

Changes to circuit sizes

Generated at commit: e2ec5c9fdf97dd3af932886bc90796a2ac0442e0, compared to commit: 046fec77631958f75356b2d2445dd269509cb954

🧾 Summary (10% most significant diffs)

Program ACIR opcodes (+/-) % Circuit size (+/-) %
brillig_keccak -57 ✅ -34.76% -57 ✅ -1.85%
brillig_nested_arrays -6 ✅ -60.00% -6 ✅ -26.09%

Full diff report 👇
Program ACIR opcodes (+/-) % Circuit size (+/-) %
bench_eddsa_poseidon 16,435 (-4) -0.02% 19,572 (-14) -0.07%
brillig_keccak 107 (-57) -34.76% 3,018 (-57) -1.85%
brillig_nested_arrays 4 (-6) -60.00% 17 (-6) -26.09%

@TomAFrench
Copy link
Member

We are often going to eliminate all usage of certain brillig functions at this step but they end up being retained (and show up in show-ssa) currently. Can you add some tracking to drop these unused functions after this pass?

@asterite
Copy link
Collaborator Author

asterite commented Nov 7, 2024

Sure!

Though... I just checked with this program:

fn main(x: Field) -> pub Field {
    let (a, _) = unsafe { identity(5) };
    assert_eq(a, 5);
    x + a
}

unconstrained fn identity(x: Field) -> (Field, Field) {
    let y = x;
    let z = y - 1;
    let w = z + 1;
    identity2(w)
}

unconstrained fn identity2(x: Field) -> (Field, Field) {
    (x, x)
}

and --show-ssa shows me this:

After Array Set Optimizations:
acir(inline) fn main f0 {
  b0(v0: Field):
    v2 = add v0, Field 5
    return v2
}

It seems after the inline pass the unused brillig functions are already being removed. Or... do you have some code where this doesn't happen?

@TomAFrench
Copy link
Member

I get the below SSA

After Inlining Const Brillig Calls:
acir(inline) fn main f0 {
  b0(v0: Field):
    v3 = add v0, Field 5
    return v3
}
brillig(inline) fn identity f1 {
  b0(v0: Field):
    v2 = sub v0, Field 1
    v3 = add v2, Field 1
    return v3, v3
}

We do remove this after the final inlining step but there's 9 passes in between these two steps so we get a good amount of unnecessary output in the SSA display

@asterite
Copy link
Collaborator Author

asterite commented Nov 7, 2024

Oooh... you mean removing them in this same pass, right? Sure, I'll do it :-)

@TomAFrench
Copy link
Member

Yep, that's it

@TomAFrench
Copy link
Member

TomAFrench commented Nov 7, 2024

One extra thing to note is that this branch panics on the testcase added in #6464 (as opposed to just compiling to incorrect code).

Obviously there's a different issue in the compiler affecting that code already but fyi.

@asterite
Copy link
Collaborator Author

asterite commented Nov 7, 2024

Ah, it's also with loop unrolling. I had to change something in this PR to workaround an issue... I guess I'll have to learn how loop unrolling works, and why it doesn't work in these brillig functions turned into ACIR.

@asterite asterite force-pushed the ab/inline-const-brillig-calls branch from 71e2b29 to 59aed41 Compare November 7, 2024 13:42
@asterite
Copy link
Collaborator Author

asterite commented Nov 7, 2024

I managed to reduce the code that triggers the panic in bignum_regression:

fn main() {
    unsafe { foo() };
}

unconstrained fn foo() {
    for i in 0..1 {
        if i == 0 {
            break;
        }
    }
}

It seems it's failing because of that break. It's likely that the optimization was made without break into account because loop unrolling only happens in ACIR code, and ACIR code doesn't allow breaks.

I can try to fix loop unrolling to account for this case, though it will likely take me a lot of time. Alternatively, we can avoid trying to inline these functions if we know they have break (for now).

Which one should I do?

@asterite
Copy link
Collaborator Author

asterite commented Nov 7, 2024

For now I pushed a commit that makes it not panic.

I wonder if loop unrolling should be able to work in two modes, ACIR or "ACIR from Brillig". That way we can still panic and use expect for the normal case, and just ignore those errors for the "ACIR from Brillig" case. What do you think?

@TomAFrench
Copy link
Member

I can try to fix loop unrolling to account for this case, though it will likely take me a lot of time. Alternatively, we can avoid trying to inline these functions if we know they have break (for now).

Which one should I do?

For now let's just skip inlining that function and we can make improvements later.

@asterite
Copy link
Collaborator Author

asterite commented Nov 7, 2024

Sounds good, done!

@asterite asterite requested a review from a team November 7, 2024 14:15
@asterite
Copy link
Collaborator Author

asterite commented Nov 7, 2024

(just asked a review to signal that this is ready for review)

@TomAFrench TomAFrench self-requested a review November 7, 2024 16:08
Copy link
Contributor

@aakoshh aakoshh left a comment

Choose a reason for hiding this comment

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

LGTM!

@asterite asterite marked this pull request as draft November 12, 2024 15:19
@asterite
Copy link
Collaborator Author

I marked this PR as draft because I want to try the other approach first (trying to invoke brillig functions, then inlining the results into the SSA and optimizing again)

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.

Inline results from brillig function calls for which all arguments are known during SSA
3 participants