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

feat(BV, CP): Add bitlist propagators for add/sub #1151

Merged
merged 2 commits into from
Jul 22, 2024

Conversation

bclement-ocp
Copy link
Collaborator

This patch adds propagators on the bitlist domain for addition and
subtraction. These propagators are able to compute low bits
independently of high bits; in particular, we now know that the sum of
two even (or two odd) numbers is even.

If there is a bit pattern that prevents carry propagation (e.g. two [0]s
for addition), we are also able to compute the following bits precisely.

Note that we do not currently decompose addition/subtraction according
to these propagators -- for instance, we do not know that
(bvadd (concat x #b0) (concat y #b0)) is (concat (bvadd x y) #b0).

Note: depends on #1058, #1083, #1084, #1085, and #1144. Only the latest commit with title "feat(CP): Add bitlist propagators for add/sub" is included in this PR.

@Halbaroth
Copy link
Collaborator

I read carefully your comments and the proofs seem okay but my concern about these comments is that I have no intuition at all after reading the proof. I basically checked them step by step. I believe we can do better and actually I found a simpler but a bit more abstract proof.

I keep your definition of carry position. I also extract your first lemma but I suggest an alternative proof:

Lemma A: for all bitvectors x and y, flipping bits from 0 to 1 in x or y does not erase carry positions in the sum x+y.

Proof: We proceed by induction on the carry position i. If i = 0, we have x_0 = y_0 = 1 and clearly switching a bit from 0 to 1 cannot remove this carry position. Let us assume we have prove the statement for any carry position < i. If x_i = y_i = 1, we proceed as before. Otherwise, we have x_i = 1 or y_i = 1 and i-1 is a carry position. By induction we know that switching a bit from 0 to 1 cannot remove the carry position i-1 and we still have x_i = 1 or y_i = 1 after this transformation. So i is still a carry position.

Now, we state the main result:

Lemma: for all bitlists a and b, we denote by a+b the set { x + y | x in a, y in b }. Then a+b is a bitlist and the position i is known for a+b if and only if i is known in a, b and the bitvectors a.bits_set + b.bits_set and a.bits_set + a.bits_unk + b.bits_set + b.bits_unk agrees at i.

Before proving this lemma, let us introduce few notations. For bitvectors x and y in a bitlist a, we write x ⇝a y if y is obtained from x by flipping one unknown bit in a from 0 to 1. For a bitlist a, we write m_a = a.bits_set and M_a = a.bits_set + a.bits_unk.

Now, remark that any value x of a is the end of a sequence m_a ⇝a ... ⇝a x and M_a is maximal in this process. So any value x+y of a+b is obtained by a sequence (m_a, m_b) ⇝ .... ⇝ (x, y) (where (u, v) ⇝ (w, z) means u ⇝a w and v ⇝b z) and M_a + M_b is maximal in this process.

Let us prove the equivalence:

  • If m_a + m_b and M_a + M_b does not agree at i, i cannot be a known bit of a+b as m_a + m_b and M_a + M_b are in a+b.
  • If i is known in a and b and m_a + m_b and M_a + M_b agrees at i and the bit is w, consider x in a and y in b. We show that x+y at i is w. The case i = 0 is easy because there is no carry involved. Assume that i > 0.
    Now, we prove that i-1 is a carry position of x+y if and only if i-1 is a carry position of m_a + m_b. By repeat lemma A, if i-1 is a carry position of x+y then i-1 is a carry position of M_a + M_b. But if i-1 is not a carry position of m_a + m_b then m_a + m_b and M_a + M_b cannot agree at i (because i is known in a and b). If i-1 is not a carry position of x+y then again by apply lemma A, i-1 is not a carry position of m_a + m_b. So the value of x+y at i is w.

Comment on lines 425 to 427
disagree (i.e. have different values for that bit). Let us furthermore
assume that [i] is known in both [a] and [b] (i.e. it is set in both
[a.bits_unk] and [b.bits_unk]).
Copy link
Collaborator

Choose a reason for hiding this comment

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

I thought that bits_unk is the set of unknown bits? Do you mean that i is not active in a.bits_unk?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, I meant that it is cleared in both. Will fix.

@@ -397,3 +397,70 @@ let bvlshr ~size:sz a b =
extract unknown 0 sz
| _ | exception Z.Overflow ->
constant Z.zero (explanation b)

let add a b =
(* A binary addition [x + y] has a carry at bit position [i] iff the addition
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should start this comment by explaining what we are proving, otherwise we have to read all the comment before discovering that it is a proof of correction.

Hence, unknown bits in [a + b] are the bits that are either unknown in
[a], unknown in [b], or differ in [a.bits_set + b.bits_set] and in
[a.bits_set + a.bits_unk + b.bits_set + b.bits_unk]. *)
let x = Z.add a.bits_set b.bits_set in
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is confusing to use x here because x denotes a completely different thing in the comment above.


Recalling [(~b).bits_set = ~(b.bits_set | b.bits_unk)], we get the formula
below. *)
let x = Z.sub a.bits_set b.bits_set in
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same remark.

This patch adds propagators on the bitlist domain for addition and
subtraction. These propagators are able to compute low bits
independently of high bits; in particular, we now know that the sum of
two even (or two odd) numbers is even.

If there is a bit pattern that prevents carry propagation (e.g. two [0]s
for addition), we are also able to compute the following bits precisely.

Note that we do not currently decompose addition/subtraction according
to these propagators -- for instance, we do not know that
`(bvadd (concat x #b0) (concat y #b0))` is `(concat (bvadd x y) #b0)`.
@bclement-ocp
Copy link
Collaborator Author

@Halbaroth Rewrote the proof taking in consideration your remarks. I tried to avoid induction since I don't think it is necessary here if we think about integers. Let me know if this is better.

Copy link
Collaborator

@Halbaroth Halbaroth left a comment

Choose a reason for hiding this comment

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

Thanks :)
Please don't merge before I finish #1169

@Halbaroth Halbaroth merged commit 3946f4e into OCamlPro:next Jul 22, 2024
14 checks passed
Halbaroth pushed a commit to Halbaroth/alt-ergo that referenced this pull request Jul 24, 2024
* feat(CP): Add bitlist propagators for add/sub

This patch adds propagators on the bitlist domain for addition and
subtraction. These propagators are able to compute low bits
independently of high bits; in particular, we now know that the sum of
two even (or two odd) numbers is even.

If there is a bit pattern that prevents carry propagation (e.g. two [0]s
for addition), we are also able to compute the following bits precisely.

Note that we do not currently decompose addition/subtraction according
to these propagators -- for instance, we do not know that
`(bvadd (concat x #b0) (concat y #b0))` is `(concat (bvadd x y) #b0)`.

* Better proof
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.

2 participants