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

Support break and continue in for loops #4259

Closed
jfecher opened this issue Feb 5, 2024 · 10 comments · Fixed by #4569
Closed

Support break and continue in for loops #4259

jfecher opened this issue Feb 5, 2024 · 10 comments · Fixed by #4569
Assignees
Labels
enhancement New feature or request

Comments

@jfecher
Copy link
Contributor

jfecher commented Feb 5, 2024

Problem

Loops in Noir do not currently support break and continue which can lead to awkward boolean flags to try to get around this. These boolean flags are often less efficient since Noir is still performing each iteration and checking a boolean flag on each iteration instead of stopping early.

Happy Case

We add break and continue to Noir, following the semantics of break and continue in Rust.

Alternatives Considered

No response

Additional Context

This was somewhat inspired by #4242 which uses a HashMap implementation where the get method is less efficient because of the lack of break.

Would you like to submit a PR for this Issue?

No

Support Needs

No response

@jfecher jfecher added the enhancement New feature or request label Feb 5, 2024
@jfecher jfecher self-assigned this Feb 5, 2024
@jfecher
Copy link
Contributor Author

jfecher commented Feb 6, 2024

where the get method is less efficient because of the lack of break.

I don't think implementing break will actually make this case more efficient unless the contents of the hash map are known at compile-time. If they aren't, break would have no way of breaking out of the loop early.

@jfecher
Copy link
Contributor Author

jfecher commented Feb 6, 2024

These could potentially be considered as an unconstrained-only feature.

@nventuro
Copy link
Contributor

nventuro commented Mar 6, 2024

This was discused offline a bit. One concern is that break may trick developers to e.g. think they're getting a performance boost because they're immediately exiting a loop - which may in term lead them to use large array sizes without much thought.

Concensus however is to add this both to ACIR and Brillig. Noir's goal is to be easy to write and have a low barrier to entry. If someone wants to then optimize their circuit they can move away from something like break on their own.

@jfecher
Copy link
Contributor Author

jfecher commented Mar 13, 2024

I think this may be potentially tricky to implement since we want two completely different behaviors depending on the runtime:

  • For brillig we should get a normal continue/break which translate to jumps
  • For acir we should lower to this intermediate variable and if statement construct

Notably, acir functions can also be run in an unconstrained context, where they should presumably have the first behavior instead.

This means we'll probably need new, dedicated continue & break instructions in ssa, but these add complexity since they're new terminator instructions. Additionally, translating these to the intermediate variable and if statement over the loop body is non-trivial I think.

@TomAFrench
Copy link
Member

Would this easier if we restricted the usage of continue and break to unconstrained functions?

@jfecher
Copy link
Contributor Author

jfecher commented Mar 15, 2024

@TomAFrench yes, quite a bit I think.

@TomAFrench
Copy link
Member

Cool, I think that it would probably be a good initial step then. We'll get the benefits of smaller bytecode for the public VM in the short term and then we can revisit whether it makes sense to have this syntax in ACIR purely for DX later on.

@TomAFrench
Copy link
Member

on a related note, we could also allow usage of the return keyword in brillig it seems.

@nventuro
Copy link
Contributor

In that case I'd consider having a constrained break produce an error message with a link to some docs that explain why breaking in a circuit does not provide any performance gains.

@TomAFrench
Copy link
Member

Yep, we should do that.

github-merge-queue bot pushed a commit that referenced this issue Mar 19, 2024
# Description

## Problem\*

Resolves #4259

## Summary\*

Implements break & continue in for loops in unconstrained code only.

When attempted to use outside of a loop or in a constrained function, an
error is issued.

## Additional Context

I didn't put a link to the docs in an error message, should I? It seems
like this may be something we may want to do in other errors as well.

This PR is currently missing documentation.
The test failures are from the change to terminator instructions. This
was needed since otherwise when break/continue set the terminator it was
being overridden when `codegen_if` would set the terminator instruction
for the same block afterward. One somewhat messy way to fix this is to
switch to an empty, unreachable block. Then the if's terminator would
set on that block instead. But if we push any instructions in between
those would be lost too and we'd be none the wiser. Although pushing
instructions after a break is usually a logic error in the compiler
anyway.

## Documentation\*

Check one:
- [ ] No documentation needed.
- [x] Documentation included in this PR.
- [ ] **[Exceptional Case]** Documentation to be submitted in a separate
PR.

# PR Checklist\*

- [x] I have tested the changes locally.
- [x] I have formatted the changes with [Prettier](https://prettier.io/)
and/or `cargo fmt` on default settings.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

3 participants