-
Notifications
You must be signed in to change notification settings - Fork 214
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(slices): Fill slice internal dummy data initial pass #3258
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's honestly a tad hard to assess if the specifics of your approach are right but I don't see an issue with the overall approach at least. I think it could benefit from separating out the steps of the algorithm more clearly though. Once the PR is finalized, some documentation on the pass would be helpful as well.
let (_, slice_sizes) = | ||
slice_sizes.split_first().expect("ICE: should be able to split slice size"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let slice_sizes = &slice_sizes[1..];
Although since you're converting to a Vec later, you may as well remove the first element like was done before then you don't have to allocate another Vec
slice_sizes.remove(0);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way I have to clone the result slices though no? Ah nvm I see your suggestion, done.
if matches!(array_value, Value::Array { .. }) && array_typ.contains_slice_element() { | ||
slice_values.push(*array); | ||
self.compute_slice_sizes(*array, &mut slice_sizes); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to mention - since it looks like you're trying to find each Value::Array
by going through get and set instructions, perhaps something like the Instruction::MakeArray
PR would be helpful for this use case? #2494. It is something that I never quite got around to finishing but it could help here since right now to find all arrays you'd need to look in all arrayget, arrayset, call, store, and load instructions to find each array while in the PR you'd only need to look for MakeArray instructions. The mem2reg pass has a similar problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm good call. I will explore this option. Right now dynamic nested slices is already leading to a lot of updates so if does not seriously reduce load I may continue with the pass as is for now and then in a later PR consider adding Instruction::MakeArray
to improve this pass as well as mem2reg.
Changes to circuit sizes
🧾 Summary (10% most significant diffs)
Full diff report 👇
|
@jfecher @guipublic @TomAFrench @kevaundray This is ready for review again. I have also made this epic to track any leftover issues with nested slices and in case any new issues come up. I left a PR description, but the comments on the pass will probably be more helpful to you. |
if let Some(value) = value { | ||
let mut slice = im::Vector::new(); | ||
match &self.inserter.function.dfg[value].clone() { | ||
Value::Array { array, .. } => { | ||
if is_parent_slice { | ||
max_size = array.len() / element_types.len(); | ||
} | ||
for i in 0..max_size { | ||
for (element_index, element_type) in | ||
element_types.iter().enumerate() | ||
{ | ||
let index_usize = i * element_types.len() + element_index; | ||
if index_usize < array.len() { | ||
slice.push_back(self.attach_slice_dummies( | ||
element_type, | ||
Some(array[index_usize]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we reduce some of this nesting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't do something like this:
let array = match &self.inserter.function.dfg[value].clone() {
Value::Array { array, .. } => {
array
}
_ => {
panic!("Expected an array value");
}
};
as array
is a temporary value that is dropped at the end of the match statement. Unless I want to sacrifice cloning each array it looks like the nesting needs to stay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can do something like
for (element_index, element_type) in
element_types.iter().enumerate()
{
let index_usize = i * element_types.len() + element_index;
let valid_index = index_usize < array.len();
let maybe_value = valid_index.then_some(array[index_usize]);
slice.push_back(self.attach_slice_dummies(
element_type,
maybe_value,
false,
max_sizes,
));
}
which removes one level from the inner loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let mut slice = im::Vector::new();
let array = match self.inserter.function.dfg[value].clone() {
Value::Array { array, .. } => array,
_ => panic!("Expected an array value"),
};
if is_parent_slice {
max_size = array.len() / element_types.len();
}
for i in 0..max_size {
for (element_index, element_type) in element_types.iter().enumerate() {
let index_usize = i * element_types.len() + element_index;
let valid_index = index_usize < array.len();
let maybe_value = valid_index.then_some(array[index_usize]);
slice.push_back(self.attach_slice_dummies(
element_type,
maybe_value,
false,
max_sizes,
));
}
}
self.inserter.function.dfg.make_array(slice, typ.clone())
This compiles for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I just needed to not borrow self.inserter.function.dfg[value]
. And creating the maybe value is much cleaner. I will switch, thanks @TomAFrench
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm we will still error out on array[index_usize]
though with this snippet:
let maybe_value = valid_index.then_some(array[index_usize]);
I will look at how we can remove the if statement before pushing though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok pushed a new version, but I had to keep an if statement around the maybe_value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as array is a temporary value that is dropped at the end of the match statement. Unless I want to sacrifice cloning each array it looks like the nesting needs to stay.
You can do that actually, since im::Vector
's Clone is O(1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can do that actually, since
im::Vector
's Clone isO(1)
I went with Tom's suggestion as simply not borrowing also worked but noted for the future.
* mv/slice-struct-fields: (112 commits) chore: Add a workflow to build with feature flag (#3378) chore: fix for-loop in aztec-library (#3377) feat!: return Pedersen structure in stdlib (#3190) feat: Manage breakpoints and allow restarting a debugging session (#3325) chore: small driver refactors (#3375) fix: fixing versioning workflow (#3296) feat!: noir-wasm outputs debug symbols (#3317) chore: build acvm_js for integration tests in parallel (#3368) chore: replace bash with `@actions/github-script` (#3369) feat(noir_js): allow providing foreign call handlers in noirJS (#3294) feat: Allow traits to have generic functions (#3365) chore(ci): ensure that acir artifacts are published on master (#3367) chore: cleanup CI workflows to be more consistent (#3366) fix: Use pedersen_hash for merkle tree (#3357) chore: format `for` stmt (#3333) fix!: move mimc to hash submodule (#3361) fix: remove sha2_block test (#3360) chore: deduplicate dependencies across the workspace (#3356) chore: Add links to complete NoirJS app code to the guide (#3359) chore: clippy fixes (#3358) ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, just some small things I noticed. I appreciate that the code was cleaned up a lot compared to the draft version
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some nits
Co-authored-by: jfecher <[email protected]>
Co-authored-by: Tom French <[email protected]>
Co-authored-by: Tom French <[email protected]>
This is ready for another look, then we can move onto #3187 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks sensible but will let others give final approval.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Co-authored-by: Tom French <[email protected]> Co-authored-by: Tom French <[email protected]> Co-authored-by: jfecher <[email protected]>
Co-authored-by: Tom French <[email protected]> Co-authored-by: Tom French <[email protected]> Co-authored-by: jfecher <[email protected]>
Description
Built off of #3187
Problem*
Works towards resolving #3188 by including work needed by the nested slices PR. This PR introduces the initial pass for filling the slices represented by SSA array values with dummy data. Filling a nested array with dummy data is necessary as when we want to dynamically index nested slices, we cannot discern which size to use for reading from memory. For example, if we have a slice of slices one of the internal slices could potentially be of size 4 while another internal slice has size 6. As we do not know which internal slice size we are using, we must read the max size. However, if we do not fill in the SSA array to account for this max size we will run into either OOB errors or reading/writing incorrect data.
Summary*
A good overview of the pass is included in the comments at the top of the pass file. The unit test at the bottom also provides a good simple overview of what the pass aims to do.
I add a pass that goes through every ArrayGet and ArraySet instruction and follows from an initial SSA value. This helps determine the max nested depth for filling dummy data inside of the nested slice.
This PR is meant to be an initial pass to get nested slice fields working with ArraySet and ArrayGet. The pass determines the max nested depth at each layer of a nested type structure. It also tracks intrinsics and the PR includes a test using slice intrinsics on a nested slice containing primitive types. Performing dynamic slice intrinsic operations using nested slice inputs is left for another PR.
Documentation
This PR requires documentation updates when merged.
Additional Context
PR Checklist*
cargo fmt
on default settings.