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: bitVec shiftLeft recurrences for bitblasting #4571

Merged
merged 51 commits into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
2fde296
stash
bollu Jul 2, 2024
b60eb5f
chore: rebase
bollu Jul 3, 2024
92fefe2
merge
bollu Jul 3, 2024
640df34
chore: fix warnings
bollu Jul 3, 2024
f1a3738
chore: minimize delta in basic, lemmas
bollu Jul 3, 2024
d87babd
chore: minimize delta in bitblast
bollu Jul 3, 2024
8ddb247
chore: cleanup, move lemmas to lemmas
bollu Jul 3, 2024
c775f5f
chore: move lemmas into lemmas
bollu Jul 3, 2024
18af03d
chore: move lemmas
bollu Jul 3, 2024
f9275e0
Update src/Init/Data/BitVec/Bitblast.lean
bollu Jul 3, 2024
b8f8a6e
Update src/Init/Data/BitVec/Bitblast.lean
bollu Jul 3, 2024
7d1918b
chore: add NOTE about extra hypothesis
bollu Jul 10, 2024
c22f3a6
chore: fix simp
bollu Jul 10, 2024
c4939d9
chore: fixup simp
bollu Jul 10, 2024
51e0c09
chore: cleanup
bollu Jul 10, 2024
f5450ae
chore: cleanup proof
bollu Jul 10, 2024
fdeecb0
chore: fixup simp
bollu Jul 22, 2024
abb6c99
chore: fuse rw
bollu Jul 22, 2024
0a6ed69
chore: fuse rw
bollu Jul 22, 2024
0da724d
chore: squeeze proof
bollu Jul 22, 2024
12b4082
chore: squeeze
bollu Jul 22, 2024
0b5b6db
chore: mark unused hypothesis as inaccessible
bollu Jul 22, 2024
cb2b544
Update src/Init/Data/BitVec/Bitblast.lean
bollu Jul 22, 2024
cce9e8f
Update src/Init/Data/BitVec/Lemmas.lean
bollu Jul 23, 2024
8009f75
chore: clean up proof of getLsb_ofNat_one to reuse getLsb_twoPow
bollu Jul 23, 2024
cebfe1b
chore: mark shiftLeft_eq' as a simp lemma
bollu Jul 23, 2024
9a73edf
chore: delete duplicate lemmas
bollu Jul 23, 2024
b5f7a9b
chore: and_twoPow_eq -> and_twoPow. Also add twoPow_and
bollu Jul 23, 2024
86ff901
chore: rephrase docstring, unsure if I got it right
bollu Jul 23, 2024
38cd01c
Update src/Init/Data/BitVec/Bitblast.lean
bollu Jul 23, 2024
5df7d21
chore: massively cleanup proof via Kim's strategy
bollu Jul 23, 2024
0393592
Update src/Init/Data/BitVec/Lemmas.lean
bollu Jul 24, 2024
6060574
chore: more cleanup
bollu Jul 24, 2024
5651e3b
chore: move lemma from bitblast into lemma, and write docstrings
bollu Jul 24, 2024
5981e7d
getLsb_ofNat_one -> getLsb_one
bollu Jul 24, 2024
805d815
chore: add docstring for shiftLeft_eq_shiftLeftRec
bollu Jul 24, 2024
f70acdc
chore: replace have heq : ...; simp [heq] anti-pattern with suffices
bollu Jul 24, 2024
c4b9473
chore: don't pass explicit params where un-necessary
bollu Jul 24, 2024
5ead5b4
chore: explain carry_of_and_eq_zero
bollu Jul 24, 2024
568864f
chore: move addition lemma next to carry
bollu Jul 24, 2024
f76acf9
chore: un-simp twoPow_zero
bollu Jul 24, 2024
83d367c
chore: final refactor
bollu Jul 24, 2024
c575ce8
chore: delete type ascription
bollu Jul 25, 2024
50f34f9
chore: change w -> w₁
bollu Jul 25, 2024
e5f9a2d
chore: delete repeated the
bollu Jul 25, 2024
d980687
chore: update comments
bollu Jul 25, 2024
47d51f9
chore: fix typo: w -> w₁
bollu Jul 25, 2024
3af6a9d
Update src/Init/Data/BitVec/Lemmas.lean
tobiasgrosser Jul 26, 2024
1517414
Update src/Init/Data/BitVec/Bitblast.lean
tobiasgrosser Jul 26, 2024
67e676c
Update src/Init/Data/BitVec/Bitblast.lean
tobiasgrosser Jul 26, 2024
e0a827f
Finish renames
tobiasgrosser Jul 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 107 additions & 2 deletions src/Init/Data/BitVec/Bitblast.lean
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,37 @@ theorem carry_succ (i : Nat) (x y : BitVec w) (c : Bool) :
exact mod_two_pow_add_mod_two_pow_add_bool_lt_two_pow_succ ..
cases x.toNat.testBit i <;> cases y.toNat.testBit i <;> (simp; omega)

/--
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 carry_of_and_eq_zero {x y : BitVec w} (h : x &&& y = 0#w) : carry i x y false = false := by
induction i with
| zero => simp
| succ i ih =>
replace h := congrArg (·.getLsb i) h
simp_all [carry_succ]

/-- The final carry bit when computing `x + y + c` is `true` iff `x.toNat + y.toNat + c.toNat ≥ 2^w`. -/
theorem carry_width {x y : BitVec w} :
carry w x y c = decide (x.toNat + y.toNat + c.toNat ≥ 2^w) := by
simp [carry]

/--
If `x &&& y = 0`, then addition does not overflow, and thus `(x + y).toNat = x.toNat + y.toNat`.
-/
theorem toNat_add_eq_toNat_add_toNat_of_and_eq_zero {x y : BitVec w} (h : x &&& y = 0#w) :
Copy link
Collaborator

Choose a reason for hiding this comment

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

toNat_add_of_and_eq_zero will suffice: if there's only one sensible RHS, it doesn't need to be described in the name.

(x + y).toNat = x.toNat + y.toNat := by
rw [toNat_add]
apply Nat.mod_eq_of_lt
suffices ¬ decide (x.toNat + y.toNat + false.toNat ≥ 2^w) by
simp only [decide_eq_true_eq] at this
omega
rw [← carry_width]
simp [not_eq_true, carry_of_and_eq_zero h]

/-- Carry function for bitwise addition. -/
def adcb (x y c : Bool) : Bool × Bool := (atLeastTwo x y c, Bool.xor x (Bool.xor y c))

Expand Down Expand Up @@ -290,7 +321,7 @@ theorem zeroExtend_truncate_succ_eq_zeroExtend_truncate_add_twoPow (x : BitVec w
simp [hik', hik'']
· ext k
simp
omega
by_cases hi : x.getLsb i <;> simp [hi] <;> omega
bollu marked this conversation as resolved.
Show resolved Hide resolved

/--
Recurrence lemma: multiplying `l` with the first `s` bits of `r` is the
Expand All @@ -314,7 +345,7 @@ theorem mulRec_eq_mul_signExtend_truncate (l r : BitVec w) (s : Nat) :
have heq :
(if r.getLsb (s' + 1) = true then l <<< (s' + 1) else 0) =
(l * (r &&& (BitVec.twoPow w (s' + 1)))) := by
simp only [ofNat_eq_ofNat, and_twoPow_eq]
simp only [ofNat_eq_ofNat, and_twoPow]
by_cases hr : r.getLsb (s' + 1) <;> simp [hr]
rw [heq, ← BitVec.mul_add, ← zeroExtend_truncate_succ_eq_zeroExtend_truncate_add_twoPow]

Expand All @@ -326,4 +357,78 @@ theorem getLsb_mul (x y : BitVec w) (i : Nat) :
· simp
· omega

/-! ## 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`.

This in conjecton with equations `shiftLeftRec_zero`, `shiftLeftRec_succ`
allows us to unfold `shiftLeft` into a circuit for bitblasting.
tobiasgrosser marked this conversation as resolved.
Show resolved Hide resolved
-/
def shiftLeftRec (x : BitVec w₁) (y : BitVec w₂) (n : Nat) : BitVec w₁ :=
let shiftAmt := (y &&& (twoPow w₂ n))
match n with
| 0 => x <<< shiftAmt
| n + 1 => (shiftLeftRec x y n) <<< shiftAmt
bollu marked this conversation as resolved.
Show resolved Hide resolved

@[simp]
theorem shiftLeftRec_zero {x : BitVec w₁} {y : BitVec w₂} :
shiftLeftRec x y 0 = x <<< (y &&& twoPow w₂ 0) := by
simp [shiftLeftRec]

@[simp]
theorem shiftLeftRec_succ {x : BitVec w₁} {y : BitVec w₂} :
shiftLeftRec x y (n + 1) = (shiftLeftRec x y n) <<< (y &&& twoPow w₂ (n + 1)) := by
simp [shiftLeftRec]

/--
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 shiftLeft_or_eq_shiftLeft_shiftLeft_of_and_eq_zero {x : BitVec w₁} {y z : BitVec w₂}
tobiasgrosser marked this conversation as resolved.
Show resolved Hide resolved
(h : y &&& z = 0#w₂) :
x <<< (y ||| z) = x <<< y <<< z := by
rw [← add_eq_or_of_and_eq_zero _ _ h,
shiftLeft_eq', toNat_add_eq_toNat_add_toNat_of_and_eq_zero h]
simp [shiftLeft_add]

/--
`shiftLeftRec x y n` shifts `x` to the left by the first `n` bits of `y`.
-/
theorem shiftLeftRec_eq {x : BitVec w₁} {y : BitVec w₂} {n : Nat} :
shiftLeftRec x y n = x <<< (y.truncate (n + 1)).zeroExtend w₂ := by
induction n generalizing x y
case zero =>
ext i
simp only [shiftLeftRec_zero, twoPow_zero, Nat.reduceAdd, truncate_one_eq_ofBool_getLsb]
suffices (y &&& 1#w₂) = zeroExtend w₂ (ofBool (y.getLsb 0)) by simp [this]
ext i
by_cases h : (↑i : Nat) = 0
· simp [h, Bool.and_comm]
· simp [h]; omega
case succ n ih =>
simp only [shiftLeftRec_succ, and_twoPow]
rw [ih]
by_cases h : y.getLsb (n + 1)
· simp only [h, ↓reduceIte]
rw [zeroExtend_truncate_succ_eq_zeroExtend_truncate_or_twoPow_of_getLsb_true h,
shiftLeft_or_eq_shiftLeft_shiftLeft_of_and_eq_zero]
simp
· simp only [h, false_eq_true, ↓reduceIte, shiftLeft_zero']
rw [zeroExtend_truncate_succ_eq_zeroExtend_truncate_of_getLsb_false (i := n + 1)]
simp [h]

/--
Show that `x <<< y` can be written in terms of `shiftLeftRec`.
This can be unfolded in terms of `shiftLeftRec_zero`, `shiftLeftRec_succ` for bitblasting.
-/
theorem shiftLeft_eq_shiftLeftRec (x : BitVec w₁) (y : BitVec w₂) :
x <<< y = shiftLeftRec x y (w₂ - 1) := by
rcases w₂ with rfl | w₂
· simp [of_length_zero]
· simp [shiftLeftRec_eq]

end BitVec
47 changes: 46 additions & 1 deletion src/Init/Data/BitVec/Lemmas.lean
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,12 @@ theorem zeroExtend_ofNat_one_eq_ofNat_one_of_lt {v w : Nat} (hv : 0 < v) :
have hv := Nat.testBit_one_eq_true_iff_self_eq_zero.mp hi₁
omega

/-- Truncating to width 1 produces a bitvector equal to the least significant bit. -/
theorem truncate_one_eq_ofBool_getLsb {x : BitVec w} :
tobiasgrosser marked this conversation as resolved.
Show resolved Hide resolved
x.truncate 1 = ofBool (x.getLsb 0) := by
ext i
simp [show i = 0 by omega]

/-! ## extractLsb -/

@[simp]
Expand Down Expand Up @@ -531,6 +537,11 @@ theorem and_assoc (x y z : BitVec w) :
ext i
simp [Bool.and_assoc]

theorem and_comm (x y : BitVec w) :
x &&& y = y &&& x := by
ext i
simp [Bool.and_comm]

/-! ### xor -/

@[simp] theorem toNat_xor (x y : BitVec v) :
Expand Down Expand Up @@ -626,6 +637,10 @@ theorem shiftLeft_zero_eq (x : BitVec w) : x <<< 0 = x := by
apply eq_of_toNat_eq
simp

@[simp]
theorem zero_shiftLeft (n : Nat) : 0#w <<< n = 0#w := by
simp [bv_toNat]

@[simp] theorem getLsb_shiftLeft (x : BitVec m) (n) :
getLsb (x <<< n) i = (decide (i < m) && !decide (i < n) && getLsb x (i - n)) := by
rw [← testBit_toNat, getLsb]
Expand Down Expand Up @@ -691,6 +706,22 @@ theorem shiftLeft_shiftLeft {w : Nat} (x : BitVec w) (n m : Nat) :
(x <<< n) <<< m = x <<< (n + m) := by
rw [shiftLeft_add]

/-! ### shiftLeft reductions from BitVec to Nat -/

@[simp]
theorem shiftLeft_eq' {x : BitVec w₁} {y : BitVec w₂} : x <<< y = x <<< y.toNat := by rfl

@[simp]
theorem shiftLeft_zero' {x : BitVec w₁} : x <<< 0#w₂ = x := by simp

theorem shiftLeft_shiftLeft' {x : BitVec w₁} {y : BitVec w₂} {z : BitVec w₃} :
x <<< y <<< z = x <<< (y.toNat + z.toNat) := by
simp [shiftLeft_add]

theorem getLsb_shiftLeft' {x : BitVec w₁} {y : BitVec w₂} {i : Nat} :
(x <<< y).getLsb i = (decide (i < w₁) && !decide (i < y.toNat) && x.getLsb (i - y.toNat)) := by
simp [shiftLeft_eq', getLsb_shiftLeft]

/-! ### ushiftRight -/

@[simp, bv_toNat] theorem toNat_ushiftRight (x : BitVec n) (i : Nat) :
Expand Down Expand Up @@ -1452,12 +1483,18 @@ theorem getLsb_twoPow (i j : Nat) : (twoPow w i).getLsb j = ((i < w) && (i = j))
simp at hi
simp_all

theorem and_twoPow_eq (x : BitVec w) (i : Nat) :
@[simp]
theorem and_twoPow (x : BitVec w) (i : Nat) :
x &&& (twoPow w i) = if x.getLsb i then twoPow w i else 0#w := by
ext j
simp only [getLsb_and, getLsb_twoPow]
by_cases hj : i = j <;> by_cases hx : x.getLsb i <;> simp_all

@[simp]
theorem twoPow_and (x : BitVec w) (i : Nat) :
(twoPow w i) &&& x = if x.getLsb i then twoPow w i else 0#w := by
rw [BitVec.and_comm, and_twoPow]

@[simp]
theorem mul_twoPow_eq_shiftLeft (x : BitVec w) (i : Nat) :
x * (twoPow w i) = x <<< i := by
Expand All @@ -1471,6 +1508,14 @@ theorem mul_twoPow_eq_shiftLeft (x : BitVec w) (i : Nat) :
apply Nat.pow_dvd_pow 2 (by omega)
simp [Nat.mul_mod, hpow]

theorem twoPow_zero {w : Nat} : twoPow w 0 = 1#w := by
apply eq_of_toNat_eq
simp

@[simp]
theorem getLsb_one {w i : Nat} : (1#w).getLsb i = (decide (0 < w) && decide (0 = i)) := by
bollu marked this conversation as resolved.
Show resolved Hide resolved
rw [← twoPow_zero, getLsb_twoPow]

/- ### zeroExtend, truncate, and bitwise operations -/

/--
Expand Down
Loading