Skip to content

Commit 68f70e7

Browse files
committed
chore: write divmodstate explanation
1 parent 01460c0 commit 68f70e7

File tree

2 files changed

+23
-19
lines changed

2 files changed

+23
-19
lines changed

src/Init/Data/BitVec/Basic.lean

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -611,9 +611,8 @@ result of appending a single bit to the front in the naive implementation).
611611
def concat {n} (msbs : BitVec n) (lsb : Bool) : BitVec (n+1) := msbs ++ (ofBool lsb)
612612

613613
/--
614-
Left shift `x` by `1` and then bitwise-or with `b`.
615-
This makes the least significant bit as `b`, and other bits are shifted left.
616-
This contrasts to `concatBit`, which actually extends the bitvector with a new bit.
614+
`x.shiftConcat b` shifts all bits of `x` to the left by `1` and sets the least significant bit to `b`.
615+
It is a non dependent version of `concat` that does not change the total bitwidth.
617616
-/
618617
def shiftConcat (x : BitVec n) (b : Bool) : BitVec n :=
619618
x <<< 1 ||| (ofBool b).zeroExtend n

src/Init/Data/BitVec/Bitblast.lean

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -431,32 +431,34 @@ theorem shiftLeft_eq_shiftLeftRec (x : BitVec w₁) (y : BitVec w₂) :
431431

432432
/-! # udiv/urem recurrence for bitblasting
433433
434-
Let us study an instructive counterexample to the claim that
435-
`n = d * q + r` for (`0 ≤ r < d`) uniquely determining q and r *over bitvectors*.
434+
In order to prove the correctness of the division algorithm on the integers,
435+
one shows that `n.div d = q` and `n.mod d = r` iff `n = d * q + r` and `0 ≤ r < d`.
436+
Mnemonic: `n` is the numerator, `d` is the denominator, `q` is the quotient, and `r` the remainder.
437+
438+
This uniqueness property is not true for bitvectors.
439+
Let us study an instructive counterexample:
436440
437441
- Let `bitwidth = 3`
438442
- Let `n = 0, d = 3`
439-
- If we choose `q = 2, r = 2`, then d * q + r = 6 + 2 = 8 ≃ 0 (mod 8) so satisfies.
440-
- But see that `q = 0, r = 0` also satisfies, as 0 * 3 + 0 = 0.
441-
- So for (`n = 0, d = 3`), both:
442-
`q = 2, r = 2` as well as
443-
`q = 0, r = 0` are solutions!
443+
- If we choose `q = 2, r = 2`, then `d * q + r = 6 + 2 = 8 ≃ 0 (mod 8)` and (`r < d`).
444+
- But see that `q = 0, r = 0` also satisfies the constraints, as `0 * 3 + 0 = 0`.
445+
- So for (`n = 0, d = 3`), both (a) `q = 2, r = 2` as well as (b) `q = 0, r = 0` are solutions!
444446
445-
It's easy to cook up such examples, by chosing `(q, r)` for a fixed `(d, n)`
446-
such that `(d * q + r)` overflows.
447+
Such examples can be created by choosing `(q, r)` for a fixed `(d, n)`
448+
such that `(d * q + r)` overflows and wraps around to equal `n`.
447449
448450
This tells us that the division algorithm must have more restrictions that just the ones
449451
we have for natural numbers. These restrictions are captured in `DivModState.Lawful`,
450452
which captures the relationship necessary between `n, d, q, r`. The key idea is to state
451-
the relationship in terms of the `{n, d, q, r}.toNat` values, and then prove that the relationship
452-
holds for the bitvector values.
453+
the relationship in terms of the `{n, d, q, r}.toNat` values, which implies that the
454+
relationship also holds at the bitvector level.
453455
454456
References:
455457
- Fast 32-bit Division on the DSP56800E: Minimized nonrestoring division algorithm by David Baca
456458
- Bitwuzla sources for bitblasting.h
457459
-/
458460

459-
private theorem Nat.div_add_eq_left_of_lt {x y z : Nat} (hx : z ∣ x) (hy : y < z) (hz : 0 < z):
461+
private theorem Nat.div_add_eq_left_of_lt {x y z : Nat} (hx : z ∣ x) (hy : y < z) (hz : 0 < z) :
460462
(x + y) / z = x / z := by
461463
refine Nat.div_eq_of_lt_le ?lo ?hi
462464
· apply Nat.le_trans
@@ -519,9 +521,6 @@ This is a proof engineering choice: An alternative world could have
519521
520522
Instead, we choose to declare all involved bitvectors as length `w`, and then prove that
521523
the values of `r` and `n` are within the bounds of `wr` and `wn` respectively.
522-
523-
Note that `DivModState` manipulates thw widths of the remainder and the dividend,
524-
525524
-/
526525
structure DivModState.Lawful (w wr wn : Nat) (n d : BitVec w)
527526
(qr : DivModState w) : Prop where
@@ -554,7 +553,7 @@ def DivModState.init (w : Nat) : DivModState w := {
554553
r := 0#w
555554
}
556555

557-
/-- Make an initial state of the DivModState, for a given choice of `n, d, q, r`. -/
556+
/-- The initial state. -/
558557
def DivModState.Lawful.init (w : Nat) (n d : BitVec w) (hd : 0#w < d) :
559558
DivModState.Lawful w 0 w n d (DivModState.init w) := {
560559
hwrn := by omega,
@@ -593,6 +592,12 @@ theorem DivModState.umod_eq_of_lawful_zero {qr : DivModState w}
593592

594593
/-! ### LawfulShiftSubtract -/
595594

595+
/-!
596+
A `LawfulShiftSubtract` is a `Lawful` DivModState that is also a legal input to the shift subtractor.
597+
So in particular, we must have at least one dividend bit left over `(0 < wn)`
598+
to perform a round of shift subtraction.
599+
-/
600+
596601
/--
597602
The input to the shift subtractor is a legal input to `divrem`, and we also need to have an
598603
input bit to perform shift subtraction on, and thus we need `0 < wn`.

0 commit comments

Comments
 (0)