chore(ssa): Assert that array offset are not set#9948
chore(ssa): Assert that array offset are not set#9948
Conversation
|
Looks like this is for #9218 |
There was a problem hiding this comment.
⚠️ Performance Alert ⚠️
Possible performance regression was detected for benchmark 'Execution Time'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.
| Benchmark suite | Current: 5c47130 | Previous: f355e1d | Ratio |
|---|---|---|---|
rollup-checkpoint-merge |
0.004 s |
0.003 s |
1.33 |
This comment was automatically generated by workflow using github-action-benchmark.
CC: @TomAFrench
I think this could be done, though I didn't check where constants are initialized in brillig (or, well, it doesn't seem there's a single place where this happens). I think the reason is that if you have this: brillig will generate a CONST op for 10, then refer to that memory address for the array get. That I guess what you are proposing is, when in Brillig we need to generate code for the above array_get, if we see a constant index and, say, it's an array and not a slice, transform that (I'm not entirely sure what I said above is correct so someone please correct me) |
In theory we could. I originally did the optimization this way, but I cannot remember why I did not keep it that way.
Yes we cannot simply have the constants as part of the DFG and use them in Brillig. They still needed to be allocated to a register using the CONST opcode. The block in which they are allocated can have a significant affect on the byte code executed (e.g., in a loop vs. in the pre-header).
So we do already have a pre-pass on Brillig where we declare the locations to allocate constants. We can see some of the regressions when we attempted to move this logic back to Brillig gen #8532. We could just move this pass to |
|
I agree that the idea of the "offset" is confusing. But I would say this is largely due to ACIR/Brillig sharing instructions to perform array operations while their array semantics differ. In ACIR arrays are truly arrays representing a block of memory. In Brillig they are basically pointers. Thus, those pointers have their own layout and they must be appropriately offset. All this pass does is essentially pre-compute any offsets it can. So yes, this is unique to Brillig, but it also needs to occur before constant allocation. I would not want to clutter constant allocation with this logic as it is better to maintain separation of concerns. |
I wonder if we could do a bit of a slight of hand:
This way the internal memory layout of Brillig remains its own concern, and we don't need the |
There was a problem hiding this comment.
⚠️ Performance Alert ⚠️
Possible performance regression was detected for benchmark 'Test Suite Duration'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.
| Benchmark suite | Current: 5c47130 | Previous: f355e1d | Ratio |
|---|---|---|---|
test_report_AztecProtocol_aztec-packages_noir-projects_noir-protocol-circuits_crates_blob |
321 s |
253 s |
1.27 |
test_report_zkpassport_noir-ecdsa_ |
3 s |
2 s |
1.50 |
This comment was automatically generated by workflow using github-action-benchmark.
CC: @TomAFrench
|
Although I do realise this doesn't mesh well with functions like |
This feels like added complexity for little gain imo (all we get is that removed the
The key thing is that they will only be deduplicated in the DFG. Not in Brillig memory. As you said Brillig is responsible for its own memory allocation and must determine how to allocate registers for constants as this is not a concept in our SSA. I contend it would be cleaner to declare constants while still in SSA form and lower them with a single strategy in Brillig gen. I agree though that this logic could be more clearly made Brillig only.
I think it may be cleanest to still just keep the |
|
Thanks, yeah, I see that it's all about the DFG. I'll open a PR soon that removes the Here: #9956 |
|
Closing in favour of #9956 |
Description
Problem*
Part of auditing
brillig_array_get_and_set.Summary*
Add assertions that
ArrayGet::offsetandArraySet::offsetareNonein some operations where they weren't handled.For example
Instruction::has_side_effectsusesDataFlowGraph::is_safe_indexto test that a constant index is less than the length of the array, however it did not take into account that theoffsetcould have made the index equal to the logical size.Another example is
optimize_length_one_array_read: it was called after we established that theindexis not a constant, so theoffsetshould beNoneeven if the pass was executed already, and yet it received anoffsetas a parameter and used it with a constantzeroindex, which wouldn't make sense for anything butNone.We could fix this by accounting for the offset and subtracting its value. This PR instead just panics, as this pass should not be followed up by anything that accesses these functions at the moment.
Removes
offsethandling fromremove_unreachable_instructions: since it appeared underruntime.is_acir(), it should never be set anyway.Additional Context
I am not exactly sure why this pass exists if I'm honest. I understand what it's doing, thanks to @asterite having added docs during the green lighting, however during Brillig codegen we check if the instruction has offset and if the index is dynamic then we have to generate offsets anyway, so why don't we insert the necessary constants into the DFG during codegen (not Brillig opcodes to calculate them, just a new constant based on the Array/Slice type), and then avoid having to have the
offset: _pattern throughout the codebase, and having to reason about whether usingindexwithout consideringoffsetis correct or not?The fact that it can manipulate indexes also sounds like a footgun based on the experience of setting the index to 0 in #9888 causing type inconsistencies.
Documentation*
Check one:
PR Checklist*
cargo fmton default settings.