Bitblasting 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.
Main results #
x + y : BitVec w
is(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.
Bitwise addition implemented via a ripple carry adder.
Equations
- x.adc y = BitVec.iunfoldr fun (i : Fin w) (c : Bool) => BitVec.adcb (x.getLsbD ↑i) (y.getLsbD ↑i) c
Instances For
add #
Adding a bitvector to its own complement yields the all ones bitpattern
Subtracting x
from the all ones bitvector is equivalent to taking its complement
Negation #
Inequalities (le / lt) #
mul recurrence for bitblasting #
A recurrence that describes multiplication as repeated addition. Is useful for bitblasting 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 i
th bit of x
.
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 i
th bit of x
.
Equations
Instances For
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 multplication.
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 multplication.
Instances For
shiftLeft recurrence for bitblasting #
shiftLeftRec x y n
shifts x
to the left by the first n
bits of y
.
The theorem shiftLeft_eq_shiftLeftRec
proves the equivalence of (x <<< y)
and shiftLeftRec
.
Together with equations shiftLeftRec_zero
, shiftLeftRec_succ
,
this allows us to unfold shiftLeft
into a circuit for bitblasting.
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 bitblasting.
udiv/urem recurrence for bitblasting #
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
.- hdPos : 0 < args.d
The denominator is positive.
- hrLtDivisor : qr.r.toNat < args.d.toNat
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 ofn
obey the invariant for division.
Instances For
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 than 2^wr
.
The quotient is morally a Bitvec wr
, and so has value less than 2^wr
.
The low (w - wn)
bits of n
obey the invariant for division.
A lawful DivModState implies w > 0
.
Equations
- ⋯ = ⋯
Instances For
An initial value with both q, r = 0
.
Equations
- BitVec.DivModState.init w = { wn := w, wr := 0, q := 0#w, r := 0#w }
Instances For
The initial state is lawful.
Equations
- ⋯ = ⋯
Instances For
A lawful DivModState with a fully consumed dividend (wn = 0
) witnesses that the
quotient has been correctly computed.
A lawful DivModState with a fully consumed dividend (wn = 0
) witnesses that the
remainder has been correctly computed.
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
.
- hdPos : 0 < args.d
- hrLtDivisor : qr.r.toNat < args.d.toNat
- hwn_lt : 0 < qr.wn
Only perform a round of shift-subtract if we have dividend bits.
Instances For
Only perform a round of shift-subtract if we have dividend bits.
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, that tries to perform a subtract shift.
Note that this should only be called when r.msb = false
, so we 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 bitblasting, 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
)
The result of udiv
agrees with the result of the division recurrence.
The result of umod
agrees with the result of the division recurrence.
sshiftRightRec x y n
shifts x
arithmetically/signed to the right by the first n
bits of y
.
The theorem sshiftRight_eq_sshiftRightRec
proves the equivalence of (x.sshiftRight y)
and sshiftRightRec
.
Together with equations sshiftRightRec_zero
, sshiftRightRec_succ
,
this allows us to unfold sshiftRight
into a circuit for bitblasting.
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 bitblasting.
ushiftRightRec x y n
shifts x
logically to the right by the first n
bits of y
.
The theorem shiftRight_eq_ushiftRightRec
proves the equivalence
of (x >>> y)
and ushiftRightRec
.
Together with equations ushiftRightRec_zero
, ushiftRightRec_succ
,
this allows us to unfold ushiftRight
into a circuit for bitblasting.
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 bitblasting.