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

SRAI #463

Merged
merged 21 commits into from
Oct 28, 2024
Merged

SRAI #463

merged 21 commits into from
Oct 28, 2024

Conversation

zemse
Copy link
Collaborator

@zemse zemse commented Oct 25, 2024

This PR adds SRAI to shift_imm, also changes logic for SLLI and SRLI.

Closes #365

@zemse zemse requested a review from hero78119 October 25, 2024 13:35
ceno_zkvm/src/chip_handler/general.rs Outdated Show resolved Hide resolved
ceno_zkvm/src/instructions/riscv.rs Outdated Show resolved Hide resolved
ceno_zkvm/src/instructions/riscv/srai.rs Outdated Show resolved Hide resolved
ceno_zkvm/src/instructions/riscv/srai.rs Outdated Show resolved Hide resolved
@naure
Copy link
Collaborator

naure commented Oct 26, 2024

Regarding the constraints, here is a more efficient way, without division or limb decomposition.

First calculate the sign-extension, that is bits flowing in from the left.

let imm = cb.create_witin();    // UInt not necessary.
let ones = imm - 1;       // 2**shift - 1, or 00…111 in binary.
let inflow = msb * ones;  // Extend with 0s or 1s.

Calculate the bits flowing out to the right

// outflow < imm
AssertLTConfig::construct_circuit(, outflow, imm,)

Then require:

rd * imm + outflow == inflow * 2**32 + rs1

Other checks:

  • rs1 < 2**32 from register read.
  • rd < 2**32 from UInt::new.
  • shift < 32 from imm_or_funct7

@naure
Copy link
Collaborator

naure commented Oct 26, 2024

This technique can also be refactored with SRLI by setting constant inflow = 0.

@hero78119
Copy link
Collaborator

Regarding the constraints, here is a more efficient way, without division or limb decomposition.

First calculate the sign-extension, that is bits flowing in from the left.

let imm = cb.create_witin();    // UInt not necessary.
let ones = imm - 1;       // 2**shift - 1, or 00…111 in binary.
let inflow = msb * ones;  // Extend with 0s or 1s.

Calculate the bits flowing out to the right

// outflow < imm
AssertLTConfig::construct_circuit(, outflow, imm,)

Then require:

rd * imm + outflow == inflow * 2**32 + rs1

Other checks:

  • rs1 < 2**32 from register read.
  • rd < 2**32 from UInt::new.
  • shift < 32 from imm_or_funct7

What an insightful observation bro!!
It taught me something new and totally changed how I see the question 😇😇

@zemse
Copy link
Collaborator Author

zemse commented Oct 26, 2024

Thanks! I have updated the implementation

let imm = cb.create_witin(…); // UInt not necessary.

shift < 32 from imm_or_funct7

I had to keep imm as UInt so that it gets range checked to be under 32 bits.

@naure
Copy link
Collaborator

naure commented Oct 26, 2024

imm as UInt so that it gets range checked to be under 32 bits.

Unnecessary, it is a fixed value. Following the code, it will go through a cb.fetch function, where it matches the fixed column imm in the program table, which is in turn populated by imm_or_funct7, which finally computes 1 << rs1 with rs1 < 32.

@naure
Copy link
Collaborator

naure commented Oct 26, 2024

One more argument for soundness, both sides of the equation are 63 bits numbers (<2**63), fitting in Goldilocks.

rd * imm + outflow == inflow * 2**32 + rs1
32 + 31.      31.         31 + 32.     32.                (Bit widths)

@matthiasgoergens
Copy link
Collaborator

imm as UInt so that it gets range checked to be under 32 bits.

Unnecessary, it is a fixed value. Following the code, it will go through a cb.fetch function, where it matches the fixed column imm in the program table, which is in turn populated by imm_or_funct7, which finally computes 1 << rs1 with rs1 < 32.

Yes!

And part of that is arguably unnecessary, too.

|| "require_equal",
|cb| {
cb.cs
.require_zero(name_fn, a.to_monomial_form() - b.to_monomial_form())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this change have to do with SRAI?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not related to SRAI. This is for MockProver accurate error description. The way MockProver infers assert equal is by destructuring LHS,RHS = Sum(LHS, RHS). After the monomial PR, Mock Prover is not able to infer require equal correctly because to_monomial_form is performed on Sum(LHS, RHS) the result doesn't correctly destructure to what it should be.

This change can be in other PR.

@@ -757,7 +757,9 @@ pub mod fmt {
) -> String {
match expression {
Expression::WitIn(wit_in) => {
wtns.push(*wit_in);
if !wtns.contains(wit_in) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this change have to do with SRAI?

Seems like an unrelated improvement?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not related to SRAI. If Wtns(x) appears multiple times in the expression, then it is duplicated.

This change can be in other PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to review that extracted PR fast.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting unrelated changes in their own PR is best; but as a second best alternative you can also expand the PR description for this PR to include the additional unrelated changes.

@zemse
Copy link
Collaborator Author

zemse commented Oct 27, 2024

Changing imm to WitIn requires impl of UInt x WitIn (currently we just support muliply of UInt and UInt) since that is needed in SLLI. Do you think it can be followed up in other PR?

@matthiasgoergens
Copy link
Collaborator

matthiasgoergens commented Oct 27, 2024

Changing imm to WitIn requires impl of UInt x WitIn (currently we just support muliply of UInt and UInt) since that is needed in SLLI. Do you think it can be followed up in other PR?

Why don't you get that other PR into master first, if that's useful, and built this PR on that one?

@naure
Copy link
Collaborator

naure commented Oct 28, 2024

UInt x WitIn

That's uint.value() * witin.expr()

But yes that can be done in a follow up PR.

Already done.

Copy link
Collaborator

@hero78119 hero78119 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick review went through.
👍 Per offline discussion, make SLLI follow in/out flow and the final config sharing with same imm as witin is nice

ceno_zkvm/src/instructions/riscv/shift_imm.rs Outdated Show resolved Hide resolved
ceno_zkvm/src/instructions/riscv/shift_imm.rs Outdated Show resolved Hide resolved
ceno_zkvm/src/instructions/riscv/shift_imm.rs Outdated Show resolved Hide resolved
ceno_zkvm/src/instructions/riscv/shift_imm.rs Outdated Show resolved Hide resolved
Copy link
Collaborator

@naure naure left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice implementation of the arithmetic approach and nice refactoring of all three instructions!

Copy link
Collaborator

@hero78119 hero78119 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@hero78119 hero78119 enabled auto-merge (squash) October 28, 2024 10:03
@hero78119 hero78119 merged commit 47f5572 into scroll-tech:master Oct 28, 2024
6 checks passed
matthiasgoergens added a commit that referenced this pull request Oct 28, 2024
Copy-and-paste coding, again.  See 47f5572 / #463
let outflow = match I::INST_KIND {
InsnKind::SLLI => (rs1_read.as_u64() * imm as u64) >> UInt::<E>::TOTAL_BITS,
InsnKind::SRAI | InsnKind::SRLI => {
if I::INST_KIND == InsnKind::SRAI {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you mix pattern matching with if?

InsnKind::SRAI | InsnKind::SRLI => {
if I::INST_KIND == InsnKind::SRAI {
let max_signed_limb_expr = (1 << (UInt::<E>::LIMB_BITS - 1)) - 1;
config.is_lt_config.as_ref().unwrap().assign_instance(
Copy link
Collaborator

@matthiasgoergens matthiasgoergens Oct 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hero78119 We are freely mixing .unwrap()s into our code. We pay the mental tax for the extra complexity of the Result wrapper but don't actually use it.

(The unwrap might even be justified here. It's honestly hard to tell.)

matthiasgoergens added a commit that referenced this pull request Oct 28, 2024
Copy-and-paste coding, again. See
47f5572 /
#463

This PR only patches up the unnecessary clone, it doesn't fix the
code-duplication-with-small-variations that's the actual problem with
our pervasive copy-and-paste culture.
hero78119 added a commit that referenced this pull request Nov 2, 2024
To close #143 and catch up milestone

- [x] unify existing SLL/SRL to in/out flow, following #463, which save
bunch of witins allocation.
- [x] on top of in/out flow, implementing SRA opcode
- [x] documentation on in/out flow
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

srai
4 participants