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

Function to increment pointer by one word in small fields. #1933

Merged
merged 20 commits into from
Oct 29, 2024
Merged

Conversation

lvella
Copy link
Member

@lvella lvella commented Oct 21, 2024

Extracted from poseidon_bb, but can be used in poseidon2_bb, sha256bb, keccak16...

@lvella lvella requested a review from onurinanc October 21, 2024 14:44
It is most likely too complex for witgen to figure out going backwards.
@@ -0,0 +1,38 @@
/// Increments a pointer by one full 32-bit word.
Copy link
Member

Choose a reason for hiding this comment

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

Both this comment and the name of the function: it returns a constraint that forces a difference of one, it does not really increment it. This might be minor and it might also be more complicated to describe, but I was really wondering if this is a prover function that computes a witness, or if it adds/creates a constraint.

Copy link
Member Author

@lvella lvella Oct 21, 2024

Choose a reason for hiding this comment

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

On one hand, I agree this is a bi-directional set of constraints, on the other hand, I doubt I can use this function to decrement instead of increment, as it would be too hard for witgen to solve.

Do you have a suggestion for a better name?

Copy link
Member Author

Choose a reason for hiding this comment

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

I fixed the comment.

Copy link
Member Author

Choose a reason for hiding this comment

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

I ran the Gröbner Basis, and going on the opposite direction from post_low = 0, I get that I can either get pre_low = 0xfffc with overflow (the correct answer) or pre_low = -4 with no overflow (makes sense, but is forbidden by the invisible pre-condition that pre_low is in range [0, 0xfffc]).

@lvella lvella requested a review from chriseth October 21, 2024 17:09
// Increment the address in every row but the ones it is set.
std::array::map(
word_increment_ptr(high_addr, low_addr, high_addr', low_addr'),
|c| std::constraints::make_conditional(c, (1 - is_addr_set'))
Copy link
Member

@chriseth chriseth Oct 22, 2024

Choose a reason for hiding this comment

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

Now I regret putting the condition as second parameter. Maybe we can change that in a future PR.
(because it doesn't fit the "if_else")

@@ -0,0 +1,35 @@
/// Creates constraints that increments a pointer by one full 32-bit word.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Creates constraints that increments a pointer by one full 32-bit word.
/// Creates constraints that increments a pointer.

(you always increment by the abstract "one" which doesn't have a size, only the pointer has a size)

Copy link
Member

Choose a reason for hiding this comment

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

Oh wait, it increments them by the size of a word?

Copy link
Member Author

Choose a reason for hiding this comment

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

I increment by 4.

Could be an argument.

let word_increment_ptr: expr, expr, expr, expr -> Constr[] = constr |pre_high, pre_low, post_high, post_low| {
// How far away from overflowing the low limb is:
let low_diff = pre_low - (0x10000 - 4);
// Is one if low limb is about to overflow:
Copy link
Member

Choose a reason for hiding this comment

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

I still don't understand what "about to overflow" means and what the two constraints in lines 20 and 24 do exactly.

Copy link
Member Author

Choose a reason for hiding this comment

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

"about to overflow" in a sense that in the next step, the low limb will be set to zero and the carry will be added to the high limb.

Copy link
Member Author

@lvella lvella Oct 22, 2024

Choose a reason for hiding this comment

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

About lines 20;24-26, it can be seen as an is_not_zero function (could be extracted, if useful somewhere else):

let is_not_zero = constr |value| {
    // Assumes the inverse of "value" if it is not zero, otherwise undefined.
    let inverse;

    // Ensures that inverse * value is either 0 or 1:
    (inverse * value - 1) * value = 0;

    inverse * value
};

Copy link
Member Author

Choose a reason for hiding this comment

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

I placed this is_not_zero in utils, and changed is_zero implementation to use it (because it is simpler). Now I am running the tests to see if it will break anything, since it returns a degree 2 expression.

Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed low_overflow to carry, so its role is more recognizable.

// Helper to allow low_overflow to be boolean
let low_diff_inv;

low_overflow = 1 - low_diff_inv * low_diff;
Copy link
Member

Choose a reason for hiding this comment

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

The constraints here ensure that low_overflow is 1 if and only if low_diff is zero, otherwise it is 0, right? Should we also extract that into a helper function? Then we can use it for XIsZero.

Copy link
Member Author

@lvella lvella Oct 22, 2024

Choose a reason for hiding this comment

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

Yes. Just found utils::is_zero(). Will fix.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

) = 0;
// Set high limb, incremented if low overflowed:
(1 - is_addr_set') * (high_addr' - high_addr - low_overflow) = 0;
// Increment the address in every row but the ones it is set.
Copy link
Contributor

Choose a reason for hiding this comment

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

pol commit low_addr[16];
pol commit high_addr[16];

std::utils::fold(16, |i| {
        let constr_array = word_increment_ptr(high_addr[i], low_addr[i], high_addr[i]', low_addr[i]');
        std::array::map(constr_array, |c| std::constraints::make_conditional(c, used))
    }, [], |acc, constr_arr| acc + constr_arr);

It's nicely working like this so that we can increment address without overflow

Copy link
Member Author

@lvella lvella Oct 22, 2024

Choose a reason for hiding this comment

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

Looks a bit off. Didn't you mean:

let constr_array = word_increment_ptr(high_addr[i], low_addr[i], high_addr[i + 1], low_addr[i + 1]);

?

@leonardoalt
Copy link
Member

Has conflicts.
@chriseth ?

@lvella
Copy link
Member Author

lvella commented Oct 25, 2024

Has conflicts.

Fixed.

@@ -0,0 +1,38 @@
/// Creates constraints that increments a pointer by amount.
///
Copy link
Member

Choose a reason for hiding this comment

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

Should we document that post_high does not wrap around?

Copy link
Member

Choose a reason for hiding this comment

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

OK, it's actually obivous from the constraints in the docstring below.

/// - pre_low must be in range [0, 0xffff].
///
/// The pointers are given in 2 16-bit limbs. This function returns a set of constraints
/// ensuring that the following are equal:
Copy link
Member

Choose a reason for hiding this comment

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

Should we also say something about the range of the post limbs?

Copy link
Member Author

Choose a reason for hiding this comment

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

Should we? I don't think it is needed.

I think the description as 2 16-bit limbs makes it clear.

I'll add a precondition that the arithmetic should not overflow u32::MAX.

std/utils.asm Outdated Show resolved Hide resolved
let inverse;

// Ensures that "inverse * x" is either 0 or 1:
(inverse * x - 1) * x = 0;
Copy link
Member

Choose a reason for hiding this comment

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

Is this a degree-3 constraint? Is that fine for our backends?

Copy link
Member Author

Choose a reason for hiding this comment

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

The degree is 3 only if x is degree 1.

The final degree is, of course, 2*deg(x) + 1.

IIRC, 3 is the target degree we support. It works on plonky3.

Also, it seems very wrong that the user has to worry about this. It feels that the breakup of compatible polynomials should be a compilation step, possibly dependent on the constraints of the backend.

Copy link
Member Author

Choose a reason for hiding this comment

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

Create an issue about this, but I admit the thoughts in there are convoluted and needs refining: #1971

@lvella lvella requested a review from chriseth October 28, 2024 15:03
@chriseth chriseth added this pull request to the merge queue Oct 29, 2024
Merged via the queue into main with commit 60219b2 Oct 29, 2024
14 checks passed
@chriseth chriseth deleted the incr_addr branch October 29, 2024 12:45
| {
// Is the low limb at its maximum value?
let carry;
carry = std::utils::is_zero(pre_low - (0x10000 - amount));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Cool!

I would have done

force_bool(carry);
pre_low + amount = carry * 2**16 + post_low;
post_high = pre_high + carry;

Needs one less column (no need to store the inverse created in is_zero), but would assume post_low and post_high to be range-constrained! Adding two range constraints would typically be more expensive, so I like this solution more.

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.

5 participants