Documentation

Init.Data.BitVec.Bitblast

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.

Example: How bitblasting works for multiplication #

We explain how the lemmas here are used for bitblasting, by using multiplication as a prototypical example. Other bitblasters for other operations follow the same pattern. To bitblast 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 bitblast 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 bitblasters on top of each other, by reducing the multiplication bitblaster 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) bitblasted 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.get idx hidx, 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 bitblastable 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 bitblasting for all SMT-LIB2 operations.

Main results #

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.

@[reducible, inline]
abbrev Bool.atLeastTwo (a : Bool) (b : Bool) (c : Bool) :

At least two out of three booleans are true.

Equations
Instances For
    @[simp]
    theorem Bool.atLeastTwo_false_left {b : Bool} {c : Bool} :
    false.atLeastTwo b c = (b && c)
    @[simp]
    theorem Bool.atLeastTwo_false_mid {a : Bool} {c : Bool} :
    a.atLeastTwo false c = (a && c)
    @[simp]
    theorem Bool.atLeastTwo_false_right {a : Bool} {b : Bool} :
    a.atLeastTwo b false = (a && b)
    @[simp]
    theorem Bool.atLeastTwo_true_left {b : Bool} {c : Bool} :
    true.atLeastTwo b c = (b || c)
    @[simp]
    theorem Bool.atLeastTwo_true_mid {a : Bool} {c : Bool} :
    a.atLeastTwo true c = (a || c)
    @[simp]
    theorem Bool.atLeastTwo_true_right {a : Bool} {b : Bool} :
    a.atLeastTwo b true = (a || b)

    Preliminaries #

    Addition #

    def BitVec.carry {w : Nat} (i : Nat) (x : BitVec w) (y : BitVec w) (c : Bool) :

    carry i x y c returns true if the i carry bit is true when computing x + y + c.

    Equations
    Instances For
      @[simp]
      theorem BitVec.carry_zero :
      ∀ {w : Nat} {x y : BitVec w} {c : Bool}, BitVec.carry 0 x y c = c
      theorem BitVec.carry_succ {w : Nat} (i : Nat) (x : BitVec w) (y : BitVec w) (c : Bool) :
      BitVec.carry (i + 1) x y c = (x.getLsbD i).atLeastTwo (y.getLsbD i) (BitVec.carry i x y c)
      theorem BitVec.carry_succ_one {w : Nat} (i : Nat) (x : BitVec w) (h : 0 < w) :
      BitVec.carry (i + 1) x (1#w) false = decide (∀ (j : Nat), j ix.getLsbD j = true)
      theorem BitVec.carry_of_and_eq_zero {w : Nat} {i : Nat} {x : BitVec w} {y : BitVec w} (h : x &&& y = 0#w) :

      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.

      theorem BitVec.carry_width {w : Nat} {c : Bool} {x : BitVec w} {y : BitVec w} :
      BitVec.carry w x y c = decide (x.toNat + y.toNat + c.toNat 2 ^ w)

      The final carry bit when computing x + y + c is true iff x.toNat + y.toNat + c.toNat ≥ 2^w.

      theorem BitVec.toNat_add_of_and_eq_zero {w : Nat} {x : BitVec w} {y : BitVec w} (h : x &&& y = 0#w) :
      (x + y).toNat = x.toNat + y.toNat

      If x &&& y = 0, then addition does not overflow, and thus (x + y).toNat = x.toNat + y.toNat.

      def BitVec.adcb (x : Bool) (y : Bool) (c : Bool) :

      Carry function for bitwise addition.

      Equations
      Instances For
        def BitVec.adc {w : Nat} (x : BitVec w) (y : BitVec w) :

        Bitwise addition implemented via a ripple carry adder.

        Equations
        Instances For
          theorem BitVec.getLsbD_add_add_bool {w : Nat} {i : Nat} (i_lt : i < w) (x : BitVec w) (y : BitVec w) (c : Bool) :
          (x + y + BitVec.setWidth w (BitVec.ofBool c)).getLsbD i = (x.getLsbD i ^^ (y.getLsbD i ^^ BitVec.carry i x y c))
          theorem BitVec.getLsbD_add {w : Nat} {i : Nat} (i_lt : i < w) (x : BitVec w) (y : BitVec w) :
          (x + y).getLsbD i = (x.getLsbD i ^^ (y.getLsbD i ^^ BitVec.carry i x y false))
          theorem BitVec.getElem_add_add_bool {w : Nat} {i : Nat} (i_lt : i < w) (x : BitVec w) (y : BitVec w) (c : Bool) :
          (x + y + BitVec.setWidth w (BitVec.ofBool c))[i] = (x[i] ^^ (y[i] ^^ BitVec.carry i x y c))
          theorem BitVec.getElem_add {w : Nat} {i : Nat} (i_lt : i < w) (x : BitVec w) (y : BitVec w) :
          (x + y)[i] = (x[i] ^^ (y[i] ^^ BitVec.carry i x y false))
          theorem BitVec.adc_spec {w : Nat} (x : BitVec w) (y : BitVec w) (c : Bool) :
          x.adc y c = (BitVec.carry w x y c, x + y + BitVec.setWidth w (BitVec.ofBool c))
          theorem BitVec.add_eq_adc (w : Nat) (x : BitVec w) (y : BitVec w) :
          x + y = (x.adc y false).snd

          add #

          theorem BitVec.getMsbD_add {w : Nat} {i : Nat} {i_lt : i < w} {x : BitVec w} {y : BitVec w} :
          (x + y).getMsbD i = (x.getMsbD i ^^ (y.getMsbD i ^^ BitVec.carry (w - 1 - i) x y false))
          theorem BitVec.msb_add {w : Nat} {x : BitVec w} {y : BitVec w} :
          (x + y).msb = (x.msb ^^ (y.msb ^^ BitVec.carry (w - 1) x y false))
          @[simp]
          theorem BitVec.add_not_self {w : Nat} (x : BitVec w) :

          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

          theorem BitVec.add_eq_or_of_and_eq_zero {w : Nat} (x : BitVec w) (y : BitVec w) (h : x &&& y = 0#w) :
          x + y = x ||| y

          Addition of bitvectors is the same as bitwise or, if bitwise and is zero.

          Sub #

          theorem BitVec.getLsbD_sub {w : Nat} {i : Nat} {i_lt : i < w} {x : BitVec w} {y : BitVec w} :
          (x - y).getLsbD i = (x.getLsbD i ^^ ((~~~y + 1#w).getLsbD i ^^ BitVec.carry i x (~~~y + 1#w) false))
          theorem BitVec.getMsbD_sub {w : Nat} {i : Nat} {i_lt : i < w} {x : BitVec w} {y : BitVec w} :
          (x - y).getMsbD i = (x.getMsbD i ^^ ((~~~y + 1).getMsbD i ^^ BitVec.carry (w - 1 - i) x (~~~y + 1) false))
          theorem BitVec.msb_sub {w : Nat} {x : BitVec w} {y : BitVec w} :
          (x - y).msb = (x.msb ^^ ((~~~y + 1#w).msb ^^ BitVec.carry (w - 1 - 0) x (~~~y + 1#w) false))

          Negation #

          theorem BitVec.bit_not_testBit {w : Nat} (x : BitVec w) (i : Fin w) :
          (BitVec.iunfoldr (fun (i : Fin w) (c : Unit) => (c, !x.getLsbD i)) ()).snd.getLsbD i = !x.getLsbD i
          theorem BitVec.bit_not_add_self {w : Nat} (x : BitVec w) :
          (BitVec.iunfoldr (fun (i : Fin w) (c : Unit) => (c, !x.getLsbD i)) ()).snd + x = -1
          theorem BitVec.bit_not_eq_not {w : Nat} (x : BitVec w) :
          (BitVec.iunfoldr (fun (i : Fin w) (c : Unit) => (c, !x.getLsbD i)) ()).snd = ~~~x
          theorem BitVec.bit_neg_eq_neg {w : Nat} (x : BitVec w) :
          -x = ((BitVec.iunfoldr (fun (i : Fin w) (c : Unit) => (c, !x.getLsbD i)) ()).snd.adc (1#w) false).snd
          theorem BitVec.getLsbD_neg {w : Nat} {i : Nat} {x : BitVec w} :
          (-x).getLsbD i = (x.getLsbD i ^^ decide (i < w) && decide (∃ (j : Nat), j < i x.getLsbD j = true))

          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 -x is the same as the least significant bit of x, and
          • The i+1-th least significant bit of -x is the complement of the i+1-th bit of x, unless all of the preceding bits are false, in which case the bit is equal to the i+1-th bit of x
          theorem BitVec.getMsbD_neg {w : Nat} {i : Nat} {x : BitVec w} :
          (-x).getMsbD i = (x.getMsbD i ^^ decide (∃ (j : Nat), j < w i < j x.getMsbD j = true))
          theorem BitVec.msb_neg {w : Nat} {x : BitVec w} :
          (-x).msb = (x != 0#w && x != BitVec.intMin w ^^ x.msb)

          abs #

          theorem BitVec.msb_abs {w : Nat} {x : BitVec w} :
          x.abs.msb = (decide (x = BitVec.intMin w) && decide (0 < w))

          Inequalities (le / lt) #

          theorem BitVec.ult_eq_not_carry {w : Nat} (x : BitVec w) (y : BitVec w) :
          x.ult y = !BitVec.carry w x (~~~y) true
          theorem BitVec.ule_eq_not_ult {w : Nat} (x : BitVec w) (y : BitVec w) :
          x.ule y = !y.ult x
          theorem BitVec.ule_eq_carry {w : Nat} (x : BitVec w) (y : BitVec w) :
          x.ule y = BitVec.carry w y (~~~x) true
          theorem BitVec.slt_eq_ult_of_msb_eq {w : Nat} {x : BitVec w} {y : BitVec w} (h : x.msb = y.msb) :
          x.slt y = x.ult y

          If two bitvectors have the same msb, then signed and unsigned comparisons coincide

          theorem BitVec.ult_eq_msb_of_msb_neq {w : Nat} {x : BitVec w} {y : BitVec w} (h : x.msb y.msb) :
          x.ult y = y.msb

          If two bitvectors have different msbs, then unsigned comparison is determined by this bit

          theorem BitVec.slt_eq_not_ult_of_msb_neq {w : Nat} {x : BitVec w} {y : BitVec w} (h : x.msb y.msb) :
          x.slt y = !x.ult y

          If two bitvectors have different msbs, then signed and unsigned comparisons are opposites

          theorem BitVec.slt_eq_ult {w : Nat} (x : BitVec w) (y : BitVec w) :
          x.slt y = (x.msb != y.msb ^^ x.ult y)
          theorem BitVec.slt_eq_not_carry {w : Nat} (x : BitVec w) (y : BitVec w) :
          x.slt y = (x.msb == y.msb ^^ BitVec.carry w x (~~~y) true)
          theorem BitVec.sle_eq_not_slt {w : Nat} (x : BitVec w) (y : BitVec w) :
          x.sle y = !y.slt x
          theorem BitVec.sle_eq_carry {w : Nat} (x : BitVec w) (y : BitVec w) :
          x.sle y = !(x.msb == y.msb ^^ BitVec.carry w y (~~~x) true)

          mul recurrence for bitblasting #

          def BitVec.mulRec {w : Nat} (x : BitVec w) (y : BitVec w) (s : Nat) :

          A recurrence that describes multiplication as repeated addition. Is useful for bitblasting multiplication.

          Equations
          • x.mulRec y 0 = if y.getLsbD 0 = true then x <<< 0 else 0
          • x.mulRec y s_2.succ = x.mulRec y s_2 + if y.getLsbD s_2.succ = true then x <<< s_2.succ else 0
          Instances For
            theorem BitVec.mulRec_zero_eq {w : Nat} (x : BitVec w) (y : BitVec w) :
            x.mulRec y 0 = if y.getLsbD 0 = true then x else 0
            theorem BitVec.mulRec_succ_eq {w : Nat} (x : BitVec w) (y : BitVec w) (s : Nat) :
            x.mulRec y (s + 1) = x.mulRec y s + if y.getLsbD (s + 1) = true then x <<< (s + 1) else 0

            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.

            @[reducible, inline, deprecated BitVec.setWidth_setWidth_succ_eq_setWidth_setWidth_add_twoPow]

            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.

            Equations
            Instances For
              theorem BitVec.mulRec_eq_mul_signExtend_setWidth {w : Nat} (x : BitVec w) (y : BitVec w) (s : Nat) :
              x.mulRec y s = x * BitVec.setWidth w (BitVec.setWidth (s + 1) y)

              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.

              @[reducible, inline, deprecated BitVec.mulRec_eq_mul_signExtend_setWidth]
              abbrev BitVec.mulRec_eq_mul_signExtend_truncate {w : Nat} (x : BitVec w) (y : BitVec w) (s : Nat) :
              x.mulRec y s = x * BitVec.setWidth w (BitVec.setWidth (s + 1) y)

              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.

              Equations
              Instances For
                theorem BitVec.getLsbD_mul {w : Nat} (x : BitVec w) (y : BitVec w) (i : Nat) :
                (x * y).getLsbD i = (x.mulRec y w).getLsbD i
                theorem BitVec.getElem_mul {w : Nat} {x : BitVec w} {y : BitVec w} {i : Nat} (h : i < w) :
                (x * y)[i] = (x.mulRec y w)[i]

                shiftLeft recurrence for bitblasting #

                def BitVec.shiftLeftRec {w₁ : Nat} {w₂ : Nat} (x : BitVec w₁) (y : BitVec w₂) (n : Nat) :
                BitVec w₁

                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
                Instances For
                  @[simp]
                  theorem BitVec.shiftLeftRec_zero {w₁ : Nat} {w₂ : Nat} {x : BitVec w₁} {y : BitVec w₂} :
                  x.shiftLeftRec y 0 = x <<< (y &&& BitVec.twoPow w₂ 0)
                  @[simp]
                  theorem BitVec.shiftLeftRec_succ {w₁ : Nat} {w₂ : Nat} {n : Nat} {x : BitVec w₁} {y : BitVec w₂} :
                  x.shiftLeftRec y (n + 1) = x.shiftLeftRec y n <<< (y &&& BitVec.twoPow w₂ (n + 1))
                  theorem BitVec.shiftLeft_or_of_and_eq_zero {w₁ : Nat} {w₂ : Nat} {x : BitVec w₁} {y : BitVec w₂} {z : BitVec w₂} (h : y &&& z = 0#w₂) :
                  x <<< (y ||| z) = x <<< y <<< z

                  If y &&& z = 0, x <<< (y ||| z) = x <<< y <<< z. This follows as y &&& z = 0 implies y ||| z = y + z, and thus x <<< (y ||| z) = x <<< (y + z) = x <<< y <<< z.

                  theorem BitVec.shiftLeftRec_eq {w₁ : Nat} {w₂ : Nat} {x : BitVec w₁} {y : BitVec w₂} {n : Nat} :
                  x.shiftLeftRec y n = x <<< BitVec.setWidth w₂ (BitVec.setWidth (n + 1) y)

                  shiftLeftRec x y n shifts x to the left by the first n bits of y.

                  theorem BitVec.shiftLeft_eq_shiftLeftRec {w₁ : Nat} {w₂ : Nat} (x : BitVec w₁) (y : BitVec w₂) :
                  x <<< y = x.shiftLeftRec y (w₂ - 1)

                  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:

                  theorem BitVec.udiv_eq_of_mul_add_toNat {w : Nat} {d : BitVec w} {n : BitVec w} {q : BitVec w} {r : BitVec w} (hd : 0 < d) (hrd : r < d) (hdqnr : d.toNat * q.toNat + r.toNat = n.toNat) :
                  n / d = q

                  If the division equation d.toNat * q.toNat + r.toNat = n.toNat holds, then n.udiv d = q.

                  theorem BitVec.umod_eq_of_mul_add_toNat {w : Nat} {d : BitVec w} {n : BitVec w} {q : BitVec w} {r : BitVec w} (hrd : r < d) (hdqnr : d.toNat * q.toNat + r.toNat = n.toNat) :
                  n % d = r

                  If the division equation d.toNat * q.toNat + r.toNat = n.toNat holds, then n.umod d = r.

                  DivModState #

                  structure BitVec.DivModState (w : Nat) :

                  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
                    structure BitVec.DivModArgs (w : Nat) :

                    DivModArgs contains the arguments to a divrem call which remain constant throughout execution.

                    • n : BitVec w

                      the numerator (aka, dividend)

                    • d : BitVec w

                      the denumerator (aka, divisor)

                    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.

                      • hwrn : qr.wr + qr.wn = w

                        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.

                      • hrWidth : qr.r.toNat < 2 ^ qr.wr

                        The remainder is morally a Bitvec wr, and so has value less than 2^wr.

                      • hqWidth : qr.q.toNat < 2 ^ qr.wr

                        The quotient is morally a Bitvec wr, and so has value less than 2^wr.

                      • hdiv : args.n.toNat >>> qr.wn = args.d.toNat * qr.q.toNat + qr.r.toNat

                        The low (w - wn) bits of n obey the invariant for division.

                      Instances For
                        theorem BitVec.DivModState.Lawful.hwrn {w : Nat} {args : BitVec.DivModArgs w} {qr : BitVec.DivModState w} (self : BitVec.DivModState.Lawful args qr) :
                        qr.wr + qr.wn = w

                        The sum of widths of the dividend and remainder is w.

                        theorem BitVec.DivModState.Lawful.hdPos {w : Nat} {args : BitVec.DivModArgs w} {qr : BitVec.DivModState w} (self : BitVec.DivModState.Lawful args qr) :
                        0 < args.d

                        The denominator is positive.

                        theorem BitVec.DivModState.Lawful.hrLtDivisor {w : Nat} {args : BitVec.DivModArgs w} {qr : BitVec.DivModState w} (self : BitVec.DivModState.Lawful args qr) :
                        qr.r.toNat < args.d.toNat

                        The remainder is strictly less than the denominator.

                        theorem BitVec.DivModState.Lawful.hrWidth {w : Nat} {args : BitVec.DivModArgs w} {qr : BitVec.DivModState w} (self : BitVec.DivModState.Lawful args qr) :
                        qr.r.toNat < 2 ^ qr.wr

                        The remainder is morally a Bitvec wr, and so has value less than 2^wr.

                        theorem BitVec.DivModState.Lawful.hqWidth {w : Nat} {args : BitVec.DivModArgs w} {qr : BitVec.DivModState w} (self : BitVec.DivModState.Lawful args qr) :
                        qr.q.toNat < 2 ^ qr.wr

                        The quotient is morally a Bitvec wr, and so has value less than 2^wr.

                        theorem BitVec.DivModState.Lawful.hdiv {w : Nat} {args : BitVec.DivModArgs w} {qr : BitVec.DivModState w} (self : BitVec.DivModState.Lawful args qr) :
                        args.n.toNat >>> qr.wn = args.d.toNat * qr.q.toNat + qr.r.toNat

                        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
                          Instances For

                            The initial state is lawful.

                            Equations
                            • =
                            Instances For
                              theorem BitVec.DivModState.udiv_eq_of_lawful {w : Nat} {n : BitVec w} {d : BitVec w} {qr : BitVec.DivModState w} (h_lawful : BitVec.DivModState.Lawful { n := n, d := d } qr) (h_final : qr.wn = 0) :
                              n / d = qr.q

                              A lawful DivModState with a fully consumed dividend (wn = 0) witnesses that the quotient has been correctly computed.

                              theorem BitVec.DivModState.umod_eq_of_lawful {w : Nat} {n : BitVec w} {d : BitVec w} {qr : BitVec.DivModState w} (h : BitVec.DivModState.Lawful { n := n, d := d } qr) (h_final : qr.wn = 0) :
                              n % d = qr.r

                              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.

                              • hwrn : qr.wr + qr.wn = w
                              • hdPos : 0 < args.d
                              • hrLtDivisor : qr.r.toNat < args.d.toNat
                              • hrWidth : qr.r.toNat < 2 ^ qr.wr
                              • hqWidth : qr.q.toNat < 2 ^ qr.wr
                              • hdiv : args.n.toNat >>> qr.wn = args.d.toNat * qr.q.toNat + qr.r.toNat
                              • hwn_lt : 0 < qr.wn

                                Only perform a round of shift-subtract if we have dividend bits.

                              Instances For
                                theorem BitVec.DivModState.Poised.hwn_lt {w : Nat} {args : BitVec.DivModArgs w} {qr : BitVec.DivModState w} (self : BitVec.DivModState.Poised args qr) :
                                0 < qr.wn

                                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
                                    theorem BitVec.DivModState.toNat_shiftRight_sub_one_eq {w : Nat} {args : BitVec.DivModArgs w} {qr : BitVec.DivModState w} (h : BitVec.DivModState.Poised args qr) :
                                    args.n.toNat >>> (qr.wn - 1) = args.n.toNat >>> qr.wn * 2 + (args.n.getLsbD (qr.wn - 1)).toNat

                                    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
                                    Instances For
                                      @[simp]
                                      theorem BitVec.divRec_zero {w : Nat} {args : BitVec.DivModArgs w} (qr : BitVec.DivModState w) :
                                      BitVec.divRec 0 args qr = qr
                                      @[simp]
                                      theorem BitVec.divRec_succ {w : Nat} (m : Nat) (args : BitVec.DivModArgs w) (qr : BitVec.DivModState w) :
                                      BitVec.divRec (m + 1) args qr = BitVec.divRec m args (BitVec.divSubtractShift args qr)

                                      The output of divRec is a lawful state

                                      @[simp]
                                      theorem BitVec.wn_divRec {w : Nat} (args : BitVec.DivModArgs w) (qr : BitVec.DivModState w) :
                                      (BitVec.divRec qr.wn args qr).wn = 0

                                      The output of divRec has no more bits left to process (i.e., wn = 0)

                                      theorem BitVec.udiv_eq_divRec {w : Nat} {d : BitVec w} {n : BitVec w} (hd : 0#w < d) :
                                      let out := BitVec.divRec w { n := n, d := d } (BitVec.DivModState.init w); n / d = out.q

                                      The result of udiv agrees with the result of the division recurrence.

                                      theorem BitVec.umod_eq_divRec {w : Nat} {d : BitVec w} {n : BitVec w} (hd : 0#w < d) :
                                      let out := BitVec.divRec w { n := n, d := d } (BitVec.DivModState.init w); n % d = out.r

                                      The result of umod agrees with the result of the division recurrence.

                                      theorem BitVec.divRec_succ' {w : Nat} (m : Nat) (args : BitVec.DivModArgs w) (qr : BitVec.DivModState w) :
                                      BitVec.divRec (m + 1) args qr = let wn := qr.wn - 1; let wr := qr.wr + 1; let r' := qr.r.shiftConcat (args.n.getLsbD wn); let input := if r' < args.d then { wn := wn, wr := wr, q := qr.q.shiftConcat false, r := r' } else { wn := wn, wr := wr, q := qr.q.shiftConcat true, r := r' - args.d }; BitVec.divRec m args input
                                      def BitVec.sshiftRightRec {w₁ : Nat} {w₂ : Nat} (x : BitVec w₁) (y : BitVec w₂) (n : Nat) :
                                      BitVec w₁

                                      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
                                      Instances For
                                        @[simp]
                                        theorem BitVec.sshiftRightRec_zero_eq {w₁ : Nat} {w₂ : Nat} (x : BitVec w₁) (y : BitVec w₂) :
                                        x.sshiftRightRec y 0 = x.sshiftRight' (y &&& 1#w₂)
                                        @[simp]
                                        theorem BitVec.sshiftRightRec_succ_eq {w₁ : Nat} {w₂ : Nat} (x : BitVec w₁) (y : BitVec w₂) (n : Nat) :
                                        x.sshiftRightRec y (n + 1) = (x.sshiftRightRec y n).sshiftRight' (y &&& BitVec.twoPow w₂ (n + 1))
                                        theorem BitVec.sshiftRight'_or_of_and_eq_zero {w₁ : Nat} {w₂ : Nat} {x : BitVec w₁} {y : BitVec w₂} {z : BitVec w₂} (h : y &&& z = 0#w₂) :
                                        x.sshiftRight' (y ||| z) = (x.sshiftRight' y).sshiftRight' z

                                        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.

                                        theorem BitVec.sshiftRightRec_eq {w₁ : Nat} {w₂ : Nat} (x : BitVec w₁) (y : BitVec w₂) (n : Nat) :
                                        x.sshiftRightRec y n = x.sshiftRight' (BitVec.setWidth w₂ (BitVec.setWidth (n + 1) y))
                                        theorem BitVec.sshiftRight_eq_sshiftRightRec {w₁ : Nat} {w₂ : Nat} {i : Nat} (x : BitVec w₁) (y : BitVec w₂) :
                                        (x.sshiftRight' y).getLsbD i = (x.sshiftRightRec y (w₂ - 1)).getLsbD i

                                        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.

                                        def BitVec.ushiftRightRec {w₁ : Nat} {w₂ : Nat} (x : BitVec w₁) (y : BitVec w₂) (n : Nat) :
                                        BitVec w₁

                                        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
                                        Instances For
                                          @[simp]
                                          theorem BitVec.ushiftRightRec_zero {w₁ : Nat} {w₂ : Nat} (x : BitVec w₁) (y : BitVec w₂) :
                                          x.ushiftRightRec y 0 = x >>> (y &&& BitVec.twoPow w₂ 0)
                                          @[simp]
                                          theorem BitVec.ushiftRightRec_succ {w₁ : Nat} {w₂ : Nat} {n : Nat} (x : BitVec w₁) (y : BitVec w₂) :
                                          x.ushiftRightRec y (n + 1) = x.ushiftRightRec y n >>> (y &&& BitVec.twoPow w₂ (n + 1))
                                          theorem BitVec.ushiftRight'_or_of_and_eq_zero {w₁ : Nat} {w₂ : Nat} {x : BitVec w₁} {y : BitVec w₂} {z : BitVec w₂} (h : y &&& z = 0#w₂) :
                                          x >>> (y ||| z) = x >>> y >>> z

                                          If y &&& z = 0, x >>> (y ||| z) = x >>> y >>> z. This follows as y &&& z = 0 implies y ||| z = y + z, and thus x >>> (y ||| z) = x >>> (y + z) = x >>> y >>> z.

                                          theorem BitVec.ushiftRightRec_eq {w₁ : Nat} {w₂ : Nat} (x : BitVec w₁) (y : BitVec w₂) (n : Nat) :
                                          x.ushiftRightRec y n = x >>> BitVec.setWidth w₂ (BitVec.setWidth (n + 1) y)
                                          theorem BitVec.shiftRight_eq_ushiftRightRec {w₁ : Nat} {w₂ : Nat} (x : BitVec w₁) (y : BitVec w₂) :
                                          x >>> y = x.ushiftRightRec y (w₂ - 1)

                                          Show that x >>> y can be written in terms of ushiftRightRec. This can be unfolded in terms of ushiftRightRec_zero, ushiftRightRec_succ for bitblasting.