-
Notifications
You must be signed in to change notification settings - Fork 199
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
Explicitly specify generic numerical parameters #4633
Comments
(Large) part of #4761. This is an initial implementation of `SharedMutableStorage`, with some limitations. I think those are best worked on in follow-up PRs, once we have the bones working. The bulk of the SharedMutable pattern is in `ScheduledValueChange`, a pure Noir struct that has all of the block number related logic. `SharedMutable` then makes a state variable out of that struct, adding public storage access both in public and private (via historical reads - see #5379), and using the new `request_max_block_number` function (from #5251). I made an effort to test as much as I could of these in Noir, with partial success in the case of `SharedMutable` due to lack of certain features, notably noir-lang/noir#4652. There is also an end-to-end test that goes through two scheuled value changes, showing that scheduled values do not affect the current one. I added some inline docs but didn't include proper docsite pages yet so that we can discuss the implementation, API, etc., and make e.g. renamings less troublesome. ### Notable implementation details I chose to make the delay a type parameter instead of a value mostly because of two reasons: - it lets us nicely serialize and deserialize `ScheduledValueChange` without including this field (which we are not currently interested in storing) - it lets us declare a state variable of type `SharedMutable<T, DELAY>` without having to change the signature of the `new` function, which is automatically injected by the macro. Overall I think this is fine, especially since we may later make the delay mutable (see below), but still worth noting. Additionally, I created a simple `public_storage` module to get slightly nicer API and encapsulation. This highlighted a Noir issue (noir-lang/noir#4633), which currently only affects public historical reads but will also affect current reads once we migrate to using the AVM opcodes. ### Future work - #5491 - #5492 (this takes care of padding during storage slot allocation) - #5501 - #5493 --------- Co-authored-by: Jan Beneš <janbenes1234@gmail.com>
(Large) part of AztecProtocol/aztec-packages#4761. This is an initial implementation of `SharedMutableStorage`, with some limitations. I think those are best worked on in follow-up PRs, once we have the bones working. The bulk of the SharedMutable pattern is in `ScheduledValueChange`, a pure Noir struct that has all of the block number related logic. `SharedMutable` then makes a state variable out of that struct, adding public storage access both in public and private (via historical reads - see #5379), and using the new `request_max_block_number` function (from #5251). I made an effort to test as much as I could of these in Noir, with partial success in the case of `SharedMutable` due to lack of certain features, notably noir-lang/noir#4652. There is also an end-to-end test that goes through two scheuled value changes, showing that scheduled values do not affect the current one. I added some inline docs but didn't include proper docsite pages yet so that we can discuss the implementation, API, etc., and make e.g. renamings less troublesome. ### Notable implementation details I chose to make the delay a type parameter instead of a value mostly because of two reasons: - it lets us nicely serialize and deserialize `ScheduledValueChange` without including this field (which we are not currently interested in storing) - it lets us declare a state variable of type `SharedMutable<T, DELAY>` without having to change the signature of the `new` function, which is automatically injected by the macro. Overall I think this is fine, especially since we may later make the delay mutable (see below), but still worth noting. Additionally, I created a simple `public_storage` module to get slightly nicer API and encapsulation. This highlighted a Noir issue (noir-lang/noir#4633), which currently only affects public historical reads but will also affect current reads once we migrate to using the AVM opcodes. ### Future work - AztecProtocol/aztec-packages#5491 - AztecProtocol/aztec-packages#5492 (this takes care of padding during storage slot allocation) - AztecProtocol/aztec-packages#5501 - AztecProtocol/aztec-packages#5493 --------- Co-authored-by: Jan Beneš <janbenes1234@gmail.com>
Solution to this would end up being some form of const generics system. |
We already have const generics, we just call them numeric generics. The thing we're lacking here is just a better way to tell the compiler "this type variable must be numeric, you can safely introduce it into the expression scope." Rust does this via |
Yeah "system" -> "syntax" is closer to what I was thinking. Apologies for not being clear. |
# Description ## Problem\* Resolves #4633, but only in the elaborator as we will be moving to this new infrastructure for the frontend soon and resolving edge cases across both the old resolver and the elaborator was becoming a time sink. ## Summary\* We now have an `UnresolvedGeneric` type rather than simply representing generics using idents. ```rust pub enum UnresolvedGeneric { Variable(Ident), Numeric { ident: Ident, typ: UnresolvedType }, } ``` We also have a new `ResolvedGeneric` struct when storing generics during resolution and elaboration. We were previously storing a tuple of three elements, but now that I need to distinguish when we have a numeric generic it was simpler to make this a struct. Some example usage: ```rust // Used as a field of a struct struct Foo<let N: u64> { inner: [u64; N], } fn baz<let N: u64>() -> Foo<N> { Foo { inner: [1; N] } } // Used in an impl impl<let N: u64> Foo<N> { fn bar(self) -> u64 { N * self.inner[0] } } ``` More examples can be found in `numeric_generics` and documentation will come in a follow-up. We make sure to error out when an explicit numeric generic is used as a type. For example, this code: ```rust struct Trash<let N: u64> { a: Field, b: N, } fn id_two<let I: Field>(x: I) -> I { x } ``` <img width="749" alt="Screenshot 2024-06-04 at 5 42 53 PM" src="https://github.com/noir-lang/noir/assets/43554004/a515a508-7ea4-4a23-bfd3-6f7bbae28e55"> ## Additional Context We do not ban the old strategy for implicit numeric generics just yet as a lot of code uses it. Currently this warning is issued: <img width="965" alt="Screenshot 2024-06-04 at 11 34 19 AM" src="https://github.com/noir-lang/noir/assets/43554004/60c79337-98b2-4e70-a11d-947f6611fc41"> ## Documentation\* Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. - [X] **[For Experimental Features]** 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. --------- Co-authored-by: jfecher <jake@aztecprotocol.com>
Even though noir-lang/noir#4633 is now closed, we no longer need this function due to how github.com//pull/7169 (the sole user of the API) does historical proofs (i.e. it reads a single slot).
Even though noir-lang/noir#4633 is now closed, we no longer need this function due to how github.com/AztecProtocol/aztec-packages/pull/7169 (the sole user of the API) does historical proofs (i.e. it reads a single slot).
With noir-lang/noir#4633 merged we can now use the new `let` syntax to mark parameters as numeric. I'm not entirely sure why we need to specify it both on the type and the impl, @vezenovm could you expand on this a little bit?
With noir-lang/noir#4633 merged we can now use the new `let` syntax to mark parameters as numeric. I'm not entirely sure why we need to specify it both on the type and the impl, @vezenovm could you expand on this a little bit?
(Large) part of AztecProtocol/aztec-packages#4761. This is an initial implementation of `SharedMutableStorage`, with some limitations. I think those are best worked on in follow-up PRs, once we have the bones working. The bulk of the SharedMutable pattern is in `ScheduledValueChange`, a pure Noir struct that has all of the block number related logic. `SharedMutable` then makes a state variable out of that struct, adding public storage access both in public and private (via historical reads - see #5379), and using the new `request_max_block_number` function (from #5251). I made an effort to test as much as I could of these in Noir, with partial success in the case of `SharedMutable` due to lack of certain features, notably noir-lang/noir#4652. There is also an end-to-end test that goes through two scheuled value changes, showing that scheduled values do not affect the current one. I added some inline docs but didn't include proper docsite pages yet so that we can discuss the implementation, API, etc., and make e.g. renamings less troublesome. ### Notable implementation details I chose to make the delay a type parameter instead of a value mostly because of two reasons: - it lets us nicely serialize and deserialize `ScheduledValueChange` without including this field (which we are not currently interested in storing) - it lets us declare a state variable of type `SharedMutable<T, DELAY>` without having to change the signature of the `new` function, which is automatically injected by the macro. Overall I think this is fine, especially since we may later make the delay mutable (see below), but still worth noting. Additionally, I created a simple `public_storage` module to get slightly nicer API and encapsulation. This highlighted a Noir issue (noir-lang/noir#4633), which currently only affects public historical reads but will also affect current reads once we migrate to using the AVM opcodes. ### Future work - AztecProtocol/aztec-packages#5491 - AztecProtocol/aztec-packages#5492 (this takes care of padding during storage slot allocation) - AztecProtocol/aztec-packages#5501 - AztecProtocol/aztec-packages#5493 --------- Co-authored-by: Jan Beneš <janbenes1234@gmail.com>
Problem
I want to write functions that are generic over some parameter
N
, whereN
is not a type but a numeric value. This is currently supported only ifN
is used as an array length in a struct definition or function parameter.Happy Case
With structs
The following code fails to compile with
N is not in scope
. While perhaps convoluted, it is a real example of a pattern I'm working on in Aztec-nr:This can be currently worked around by adding a dummy array, as suggested by @jfecher:
This workaround seems good enough for use cases in which the struct has other data, as the only side effect is slightly wonky construction.
Without structs
If the
impl
functions don't receiveself
, then the workaround presented above does not work. This prevents implementing patterns of the formrange().map().reduce
i.e. doing something a number of times, collecting that into an array and then using that array to produce a value. An example of this is public storage reads:The above does not compile with
N is not in scope
for the0..N
part. I could not find a way to make it work, except by makingN
a type parameter forPublicStorage
, adding the dummy variable and makingread
takeself
(which I'd rather avoid).This has not been an issue so far because we read storage in an array, but with AztecProtocol/aztec-packages#5153 this is going to change, and it is already like that for historical public storage reads in private, which are done one at a time.
Project Impact
Blocker
The text was updated successfully, but these errors were encountered: