-
Notifications
You must be signed in to change notification settings - Fork 12
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/Opcode Circuits for DIV, REM, and REMU opcodes #596
base: master
Are you sure you want to change the base?
Feat/Opcode Circuits for DIV, REM, and REMU opcodes #596
Conversation
@bgillesp when will this pr be ready for review? |
Unit tests still need to be added for the new opcode circuits, but everything else is in shape for an initial review -- will change the PR out of Draft now. |
Implementing a TODO from #596
To help make #596 easier to read and reason about.
To make the diff for #596 easier to read.
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.
A few comments and suggestions so far.
Attach DIVU circuit statistics 🔥 First row master branch, second row this PR
|
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.
Epic job! thanks for the implementation and clear documentation.
Just do a quick overview and will proceed arithmetics constrain review 2nd round
…opcodes The current implementation tries to be generic over different UInt widths, but the circuit design soundness relies on specifically using 32-bit registers in the Goldilocks field, so it doesn't make sense to sacrifice readability for unused generality here.
…ded value The current assumption of the circuit design is that n_bits has to be 8 or 16, so this change will better flag violations of this assumption if we end up changing the base limb size to 32 bits.
Thank you for the generous feedback everyone! 🙏 I think I've addressed your comments as much as I can currently -- will keep an eye on this space to help move things forward as the review progresses. |
let neg_div_expr = { | ||
let a_neg = dividend_signed.is_negative.expr(); | ||
let b_neg = divisor_signed.is_negative.expr(); | ||
&a_neg * (1 - &b_neg) + (1 - &a_neg) * &b_neg |
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.
Really minor one, I like the original style more because It's clear and slightly efficient. It reduces the number of recursion here. But it depends on you :)
// a_neg * (1 - b_neg) + (1 - a_neg) * b_neg
(a_neg.clone() + b_neg.clone()) - (Expression::<E>::from(2) * a_neg * b_neg)
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.
Yeah, I think the fact that it was simplified to monomial form was why I went with the original version, but then the current revision is closer to the underlying logic of the expression, which gives an easily readable meaning for binary inputs. I don't think I have a strong opinion about it -- any thoughts @matthiasgoergens since the change was your suggestion?
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.
There are two parts to my suggestion. One is to remove the manual clones, and the other is to use a simpler algebraic expression.
They are independent of each other.
I don't really have much of an opinion on whether (a + b - 2 * a * b)
or a * (1 - b) + (1 - a) * b
is easier to read. I went with the latter, because someone had made a comment // a_neg * (1 - b_neg) + (1 - a_neg) * b_neg
to that end; so I assume someone thought that form is easier to read.
Here's my suggestion, if you want to use the original expression:
// a_neg xor b_neg
&a_neg + &b_neg - 2 * a_neg * b_neg
@KimiWu123 Btw, when you say 'efficient', do you mean that the distribution function will be called slightly less often; or do you mean that the final resulting expression will be different?
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.
Sure, this seems also like a good way to write it, with the simple comment -- updated to this version in the latest commit.
|
||
Ok(()) | ||
} | ||
} | ||
|
||
// TODO Tests |
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 guess you forgot this TODO
. I didn't see any testing added.
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.
Not forgotten! I mentioned that this is still TODO in the PR description -- another member of our team is working on it, but paused yesterday due to the rapid changes from the review. Should be finished relatively soon, but probably an initial review of the circuit design would still be valuable at this point before we validate with unit tests.
Implementing a TODO from #596
To make the diff for scroll-tech#596 easier to read.
To help make scroll-tech#596 easier to read and reason about. Also introduce a few more conversion helpers.
Implementing a TODO from scroll-tech#596
Note that while commit |
I clicked the button to kick off a re-run of the tests. (Is that button available for you?) |
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.
Thanks for your patience with the review process. Looking good now.
Please remove the now obsolete TODO about the LT -> Lt rename before merging.
@@ -18,6 +18,7 @@ use crate::{ | |||
|
|||
use super::SignedExtendConfig; | |||
|
|||
// TODO rename to AssertLtConfig (LT -> Lt) to fit naming conventions |
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.
// TODO rename to AssertLtConfig (LT -> Lt) to fit naming conventions |
is_dividend_max_negative.expr() * is_divisor_minus_one.expr(), | ||
)?; | ||
|
||
// For signed division overflow, dividend = -2^31 and divisor |
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 suspect we actually might be able to get away without any special handling of signed division overflow (and just get the right answer out of the general constraints).
But that's something for me to think about later, nothing that should hold up this PR.
My suspicion is because we have more range available in Goldilocks, and (i32::MIN as Goldilocks) / (-1) == 1<<31 as Goldilocks, which is the answer we want at the end.
This PR extends the existing
DIVU
opcode implementation to expose the remainder as an output, and to handle the (more complicated) signed case. See the comments indiv.rs
for details, but in summary:DIV
andREM
opcode circuits is to represent the division algorithm relationdividend = quotient * divisor + remainder
using native field elements, and constraining so that either0 <= remainder < divisor
ordivisor == 0
holds.-2^31 / -1 = 2^31
is out of the representable range fori32
values.quotient
or theremainder
value is exposed as the output registerrd
.DIV
andREM
orDIVU
andREMU
could be batched into a single circuit since both of the outputs are represented; however, this is not yet implemented.Currently the circuit implementation and documentation are finished and ready for review. Unit tests still need to be finished, but it would be great to get some eyes on the circuit design now.