Bit blasting of bitvectors #
This module provides theorems for showing the equivalence between BitVec operations using
the Fin 2^n representation and Boolean vectors. It is still under development, but
intended to provide a path for converting SAT and SMT solver proofs about BitVectors
as vectors of bits into proofs about Lean BitVec values.
The module is named for the bit-blasting operation in an SMT solver that converts bitvector expressions into expressions about individual bits in each vector.
Example: How bit blasting works for multiplication #
We explain how the lemmas here are used for bit blasting,
by using multiplication as a prototypical example.
Other bit blasters for other operations follow the same pattern.
To bit blast a multiplication of the form x * y,
we must unfold the above into a form that the SAT solver understands.
We assume that the solver already knows how to bit blast addition.
This is known to bv_decide, by exploiting the lemma add_eq_adc,
which says that x + y : BitVec w equals (adc x y false).2,
where adc builds an add-carry circuit in terms of the primitive operations
(bitwise and, bitwise or, bitwise xor) that bv_decide already understands.
In this way, we layer bit blasters on top of each other,
by reducing the multiplication bit blaster to an addition operation.
The core lemma is given by getLsbD_mul:
x y : BitVec w ⊢ (x * y).getLsbD i = (mulRec x y w).getLsbD i
Which says that the ith bit of x * y can be obtained by
evaluating the ith bit of (mulRec x y w).
Once again, we assume that bv_decide knows how to implement getLsbD,
given that mulRec can be understood by bv_decide.
We write two lemmas to enable bv_decide to unfold (mulRec x y w)
into a complete circuit, when w is a known constant. This is given by two recurrence lemmas, mulRec_zero_eqandmulRec_succ_eq, which are applied repeatedly when the width is 0and when the width isw' + 1`:
mulRec_zero_eq :
mulRec x y 0 =
if y.getLsbD 0 then x else 0
mulRec_succ_eq
mulRec x y (s + 1) =
mulRec x y s +
if y.getLsbD (s + 1) then (x <<< (s + 1)) else 0 := rfl
By repeatedly applying the lemmas mulRec_zero_eq and mulRec_succ_eq,
one obtains a circuit for multiplication.
Note that this circuit uses BitVec.add, BitVec.getLsbD, BitVec.shiftLeft.
Here, BitVec.add and BitVec.shiftLeft are (recursively) bit blasted by bv_decide,
using the lemmas add_eq_adc and shiftLeft_eq_shiftLeftRec,
and BitVec.getLsbD is a primitive that bv_decide knows how to reduce to SAT.
The two lemmas, mulRec_zero_eq, and mulRec_succ_eq,
are used in Std.Tactic.BVDecide.BVExpr.bitblast.blastMul
to prove the correctness of the circuit that is built by bv_decide.
def blastMul (aig : AIG BVBit) (input : AIG.BinaryRefVec aig w) : AIG.RefVecEntry BVBit w
theorem denote_blastMul (aig : AIG BVBit) (lhs rhs : BitVec w) (assign : Assignment) :
...
⟦(blastMul aig input).aig, (blastMul aig input).vec[idx], assign.toAIGAssignment⟧
=
(lhs * rhs).getLsbD idx
The definition and theorem above are internal to bv_decide,
and use mulRec_{zero,succ}_eq to prove that the circuit built by bv_decide
computes the correct value for multiplication.
To zoom out, therefore, we follow two steps:
First, we prove bitvector lemmas to unfold a high-level operation (such as multiplication)
into already bit blastable operations (such as addition and left shift).
We then use these lemmas to prove the correctness of the circuit that bv_decide builds.
We use this workflow to implement bit blasting for all SMT-LIB v2 operations.
Main results #
x + y : BitVec wis(adc x y false).2.
Future work #
All other operations are to be PR'ed later and are already proved in https://github.com/mhk119/lean-smt/blob/bitvec/Smt/Data/Bitwise.lean.
Preliminaries #
Addition #
If x &&& y = 0, then the carry bit (x + y + 0) is always false for any index i.
Intuitively, this is because a carry is only produced when at least two of x, y, and the
previous carry are true. However, since x &&& y = 0, at most one of x, y can be true,
and thus we never have a previous carry, which means that the sum cannot produce a carry.
Carry function for bitwise addition.
Equations
- BitVec.adcb x y c = (x.atLeastTwo y c, x ^^ (y ^^ c))
Instances For
add #
Sub #
Negation #
Remember that negating a bitvector is equal to incrementing the complement
by one, i.e., -x = ~~~x + 1. See also neg_eq_not_add.
This computation has two crucial properties:
- The least significant bit of
-xis the same as the least significant bit ofx, and - The
i+1-th least significant bit of-xis the complement of thei+1-th bit ofx, unless all of the preceding bits arefalse, in which case the bit is equal to thei+1-th bit ofx
This is false if v < w and b = intMin. See also signExtend_neg_of_ne_intMin.
This is false if v < w and b = intMin. See also signExtend_neg_of_le.
abs #
Inequalities (le / lt) #
mul recurrence for bit blasting #
A recurrence that describes multiplication as repeated addition.
This function is useful for bit blasting multiplication.
Equations
Instances For
Recurrence lemma: truncating to i+1 bits and then zero extending to w
equals truncating upto i bits [0..i-1], and then adding the ith bit of x.
Recurrence lemma: multiplying x with the first s bits of y is the
same as truncating y to s bits, then zero extending to the original length,
and performing the multiplication.
shiftLeft recurrence for bit blasting #
Shifts x to the left by the first n bits of y.
The theorem BitVec.shiftLeft_eq_shiftLeftRec proves the equivalence of (x <<< y) and
BitVec.shiftLeftRec x y.
Together with equations BitVec.shiftLeftRec_zero and BitVec.shiftLeftRec_succ, this allows
BitVec.shiftLeft to be unfolded into a circuit for bit blasting.
Equations
- x.shiftLeftRec y 0 = x <<< (y &&& BitVec.twoPow w₂ 0)
- x.shiftLeftRec y s_1.succ = x.shiftLeftRec y s_1 <<< (y &&& BitVec.twoPow w₂ s_1.succ)
Instances For
shiftLeftRec x y n shifts x to the left by the first n bits of y.
Show that x <<< y can be written in terms of shiftLeftRec.
This can be unfolded in terms of shiftLeftRec_zero, shiftLeftRec_succ for bit blasting.
udiv/urem recurrence for bit blasting #
In order to prove the correctness of the division algorithm on the integers,
one shows that n.div d = q and n.mod d = r iff n = d * q + r and 0 ≤ r < d.
Mnemonic: n is the numerator, d is the denominator, q is the quotient, and r the remainder.
This uniqueness of decomposition is not true for bitvectors.
For n = 0, d = 3, w = 3, we can write:
Such examples can be created by choosing different (q, r) for a fixed (d, n)
such that (d * q + r) overflows and wraps around to equal n.
This tells us that the division algorithm must have more restrictions than just the ones
we have for integers. These restrictions are captured in DivModState.Lawful.
The key idea is to state the relationship in terms of the toNat values of {n, d, q, r}.
If the division equation d.toNat * q.toNat + r.toNat = n.toNat holds,
then n.udiv d = q and n.umod d = r.
Following this, we implement the division algorithm by repeated shift-subtract.
References:
- Fast 32-bit Division on the DSP56800E: Minimized nonrestoring division algorithm by David Baca
- Bitwuzla sources for bitblasting.h
DivModState #
DivModState is a structure that maintains the state of recursive divrem calls.
- wn : Nat
The number of bits in the numerator that are not yet processed
- wr : Nat
The number of bits in the remainder (and quotient)
- q : BitVec w
The current quotient.
- r : BitVec w
The current remainder.
Instances For
DivModArgs contains the arguments to a divrem call which remain constant throughout
execution.
Instances For
A DivModState is lawful if the remainder width wr plus the numerator width wn equals w,
and the bitvectors r and n have values in the bounds given by bitwidths wr, resp. wn.
This is a proof engineering choice: an alternative world could have been
r : BitVec wr and n : BitVec wn, but this required much more dependent typing coercions.
Instead, we choose to declare all involved bitvectors as length w, and then prove that
the values are within their respective bounds.
We start with wn = w and wr = 0, and then in each step, we decrement wn and increment wr.
In this way, we grow a legal remainder in each loop iteration.
The sum of widths of the dividend and remainder is
w.The denominator is positive.
The remainder is strictly less than the denominator.
The remainder is morally a
Bitvec wr, and so has value less than2^wr.The quotient is morally a
Bitvec wr, and so has value less than2^wr.The low
(w - wn)bits ofnobey the invariant for division.
Instances For
A lawful DivModState implies w > 0.
Equations
- ⋯ = ⋯
Instances For
An initial value with both q, r = 0.
Instances For
The initial state is lawful.
Equations
- ⋯ = ⋯
Instances For
DivModState.Poised #
A Poised DivModState is a state which is Lawful and furthermore, has at least
one numerator bit left to process (0 < wn)
The input to the shift subtractor is a legal input to divrem, and we also need to have an
input bit to perform shift subtraction on, and thus we need 0 < wn.
Only perform a round of shift-subtract if we have dividend bits.
Instances For
In the shift subtract input, the dividend is at least one bit long (wn > 0), so
the remainder has bits to be computed (wr < w).
Equations
- ⋯ = ⋯
Instances For
Division shift subtractor #
One round of the division algorithm. It tries to perform a subtract shift.
This should only be called when r.msb = false, so it will not overflow.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The value of shifting right by wn - 1 equals shifting by wn and grabbing the lsb at (wn - 1).
We show that the output of divSubtractShift is lawful, which tells us that it
obeys the division equation.
Core division algorithm circuit #
A recursive definition of division for bit blasting, in terms of a shift-subtraction circuit.
Equations
- BitVec.divRec 0 args qr = qr
- BitVec.divRec s_1.succ args qr = BitVec.divRec s_1 args (BitVec.divSubtractShift args qr)
Instances For
The output of divRec is a lawful state
The output of divRec has no more bits left to process (i.e., wn = 0)
Shifts x arithmetically (signed) to the right by the first n bits of y.
The theorem BitVec.sshiftRight_eq_sshiftRightRec proves the equivalence of (x.sshiftRight y) and
BitVec.sshiftRightRec x y. Together with equations BitVec.sshiftRightRec_zero, and
BitVec.sshiftRightRec_succ, this allows BitVec.sshiftRight to be unfolded into a circuit for
bit blasting.
Equations
- x.sshiftRightRec y 0 = x.sshiftRight' (y &&& BitVec.twoPow w₂ 0)
- x.sshiftRightRec y s_1.succ = (x.sshiftRightRec y s_1).sshiftRight' (y &&& BitVec.twoPow w₂ s_1.succ)
Instances For
If y &&& z = 0, x.sshiftRight (y ||| z) = (x.sshiftRight y).sshiftRight z.
This follows as y &&& z = 0 implies y ||| z = y + z,
and thus x.sshiftRight (y ||| z) = x.sshiftRight (y + z) = (x.sshiftRight y).sshiftRight z.
Show that x.sshiftRight y can be written in terms of sshiftRightRec.
This can be unfolded in terms of sshiftRightRec_zero_eq, sshiftRightRec_succ_eq for bit blasting.
Shifts x logically to the right by the first n bits of y.
The theorem BitVec.shiftRight_eq_ushiftRightRec proves the equivalence
of (x >>> y) and BitVec.ushiftRightRec.
Together with equations BitVec.ushiftRightRec_zero and BitVec.ushiftRightRec_succ,
this allows BitVec.ushiftRight to be unfolded into a circuit for bit blasting.
Equations
- x.ushiftRightRec y 0 = x >>> (y &&& BitVec.twoPow w₂ 0)
- x.ushiftRightRec y s_1.succ = x.ushiftRightRec y s_1 >>> (y &&& BitVec.twoPow w₂ s_1.succ)
Instances For
Show that x >>> y can be written in terms of ushiftRightRec.
This can be unfolded in terms of ushiftRightRec_zero, ushiftRightRec_succ for bit blasting.
Overflow definitions #
Mappings to and from BitVec #
the most significant bit of the signed division x.sdiv y can be computed
by the following cases:
(1) x nonneg, y nonneg: never neg.
(2) x nonneg, y neg: neg when result nonzero.
We know that y is nonzero since it is negative, so we only check |x| ≥ |y|.
(3) x neg, y nonneg: neg when result nonzero.
We check that y ≠ 0 and |x| ≥ |y|.
(4) x neg, y neg: neg when x = intMin, y = -1, since intMin / -1 = intMin`.
The proof strategy is to perform a case analysis on the sign of x and y,
followed by unfolding the sdiv into udiv.
Lemmas that use bit blasting circuits #
The value of (carry i x y false) can be computed by truncating x and y
to len bits where len ≥ i.
The [0..len) low bits of x + y can be computed by truncating x and y
to len bits and then adding.
extractLsb' commutes with multiplication.
Adding bitvectors that are zero in complementary positions equals concatenation.
We add a no_index annotation to HAppend.hAppend such that the width v + w
does not act as a key in the discrimination tree.
This is important to allow matching, when the type of the result of append
x : BitVec 3 and y : BitVec 4 has been reduced to x ++ y : BitVec 7.
Note [Fast Unsigned Multiplication Overflow Detection] #
The fast unsigned multiplication overflow detection circuit is described in
Efficient integer multiplication overflow detection circuits (https://ieeexplore.ieee.org/abstract/document/987767).
With this circuit, the computation of the overflow flag for the unsigned multiplication of
two bitvectors x and y with bitwidth w requires:
· extending the operands by 1 bit and performing the multiplication with the extended operands,
· computing the preliminary overflow flag, which describes whether x and y together have at most
w - 2 leading zeros.
If the most significant bit of the extended operands' multiplication is true or if the
preliminary overflow flag is true, overflow happens.
In particular, the conditions check two different cases:
· if the most significant bit of the extended operands' multiplication is true, the result of the
multiplication 2 ^ w ≤ x.toNat * y.toNat < 2 ^ (w + 1),
· if the preliminary flag is true, then 2 ^ (w + 1) ≤ x.toNat * y.toNat.
The computation of the preliminary overflow flag resRec relies on two quantities:
· uppcRec: the unsigned parallel prefix circuit for the bits until a certain i,
· aandRec: the conjunction between the parallel prefix circuit at of the first operand until a certain i
and the i-th bit in the second operand.
Preliminary overflow flag for fast umulOverflow circuit as introduced in
Efficient integer multiplication overflow detection circuits (https://ieeexplore.ieee.org/abstract/document/987767).
Equations
Instances For
If the sum of the leading zeroes of two bitvecs with bitwidth w is less than or equal to
(w - 2), then the preliminary overflow flag is true and their unsigned multiplication overflows.
The explanation is in Efficient integer multiplication overflow detection circuits
https://ieeexplore.ieee.org/abstract/document/987767