Skip to content

Commit 62f3ee2

Browse files
luisacicolinitobiasgrosserbollu
authored
feat: add leading zero counter BitVec.clz and bitblaster circuit/infrastructure (#8546)
This PR adds a new `BitVec.clz` operation and a corresponding `clz` circuit to `bv_decide`, allowing to bitblast the count leading zeroes operation. The AIG circuit is linear in the number of bits of the original expression, making the bitblasting convenient wrt. rewriting. `clz` is common in numerous compiler intrinsics (see [here](https://clang.llvm.org/docs/LanguageExtensions.html#intrinsics-support-within-constant-expressions)) and architectures (see [here](https://en.wikipedia.org/wiki/Find_first_set)). Co-authored by @bollu. --------- Co-authored-by: Tobias Grosser <[email protected]> Co-authored-by: Siddharth <[email protected]>
1 parent e8c8261 commit 62f3ee2

File tree

12 files changed

+269
-1
lines changed

12 files changed

+269
-1
lines changed

src/Init/Data/BitVec/Basic.lean

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,4 +850,15 @@ treating `x` and `y` as 2's complement signed bitvectors.
850850
def smulOverflow {w : Nat} (x y : BitVec w) : Bool :=
851851
(x.toInt * y.toInt ≥ 2 ^ (w - 1)) || (x.toInt * y.toInt < - 2 ^ (w - 1))
852852

853+
/-- Count the number of leading zeros downward from the `n`-th bit to the `0`-th bit for the bitblaster.
854+
This builds a tree of `if-then-else` lookups whose length is linear in the bitwidth,
855+
and an efficient circuit for bitblasting `clz`. -/
856+
def clzAuxRec {w : Nat} (x : BitVec w) (n : Nat) : BitVec w :=
857+
match n with
858+
| 0 => if x.getLsbD 0 then BitVec.ofNat w (w - 1) else BitVec.ofNat w w
859+
| n' + 1 => if x.getLsbD n then BitVec.ofNat w (w - 1 - n) else clzAuxRec x n'
860+
861+
/-- Count the number of leading zeros. -/
862+
def clz (x : BitVec w) : BitVec w := clzAuxRec x (w - 1)
863+
853864
end BitVec

src/Init/Data/BitVec/Lemmas.lean

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3468,6 +3468,11 @@ theorem ofNat_sub_ofNat {n} (x y : Nat) : BitVec.ofNat n x - BitVec.ofNat n y =
34683468
apply eq_of_toNat_eq
34693469
simp [BitVec.ofNat, Fin.ofNat_sub]
34703470

3471+
theorem ofNat_sub_ofNat_of_le (x y : Nat) (hy : y < 2 ^ w) (hlt : y ≤ x):
3472+
BitVec.ofNat w x - BitVec.ofNat w y = BitVec.ofNat w (x - y) := by
3473+
apply eq_of_toNat_eq
3474+
simp [Nat.mod_eq_of_lt hy, show 2 ^ w - y + x = 2 ^ w + (x - y) by omega, Nat.add_mod_left]
3475+
34713476
@[simp] protected theorem sub_zero (x : BitVec n) : x - 0#n = x := by apply eq_of_toNat_eq ; simp
34723477

34733478
@[simp] protected theorem zero_sub (x : BitVec n) : 0#n - x = -x := rfl
@@ -5625,6 +5630,23 @@ theorem msb_replicate {n w : Nat} {x : BitVec w} :
56255630
simp only [BitVec.msb, getMsbD_replicate, Nat.zero_mod]
56265631
cases n <;> cases w <;> simp
56275632

5633+
/-! ### Count leading zeros -/
5634+
5635+
theorem clzAuxRec_zero (x : BitVec w) :
5636+
x.clzAuxRec 0 = if x.getLsbD 0 then BitVec.ofNat w (w - 1) else BitVec.ofNat w w := by rfl
5637+
5638+
theorem clzAuxRec_succ (x : BitVec w) :
5639+
x.clzAuxRec (n + 1) = if x.getLsbD (n + 1) then BitVec.ofNat w (w - 1 - (n + 1)) else BitVec.clzAuxRec x n := by rfl
5640+
5641+
theorem clzAuxRec_eq_clzAuxRec_of_le (x : BitVec w) (h : w - 1 ≤ n) :
5642+
x.clzAuxRec n = x.clzAuxRec (w - 1) := by
5643+
let k := n - (w - 1)
5644+
rw [show n = (w - 1) + k by omega]
5645+
induction k
5646+
· case zero => simp
5647+
· case succ k ihk =>
5648+
simp [show w - 1 + (k + 1) = (w - 1 + k) + 1 by omega, clzAuxRec_succ, ihk,
5649+
show x.getLsbD (w - 1 + k + 1) = false by simp only [show w ≤ w - 1 + k + 1 by omega, getLsbD_of_ge]]
56285650

56295651
/-! ### Inequalities (le / lt) -/
56305652

src/Lean/Elab/Tactic/BVDecide/Frontend/BVDecide/Reflect.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ instance : ToExpr BVUnOp where
4040
| .rotateRight n => mkApp (mkConst ``BVUnOp.rotateRight) (toExpr n)
4141
| .arithShiftRightConst n => mkApp (mkConst ``BVUnOp.arithShiftRightConst) (toExpr n)
4242
| .reverse => mkConst ``BVUnOp.reverse
43+
| .clz => mkConst ``BVUnOp.clz
4344
toTypeExpr := mkConst ``BVUnOp
4445

4546
instance : ToExpr (BVExpr w) where

src/Lean/Elab/Tactic/BVDecide/Frontend/BVDecide/Reify.lean

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ where
184184
return some atom
185185
| BitVec.reverse _ innerExpr =>
186186
unaryReflection innerExpr .reverse ``Std.Tactic.BVDecide.Reflect.BitVec.reverse_congr origExpr
187+
| BitVec.clz _ innerExpr =>
188+
unaryReflection innerExpr .clz ``Std.Tactic.BVDecide.Reflect.BitVec.clz_congr origExpr
187189
| _ => return none
188190

189191
/--

src/Lean/Meta/Tactic/Simp/BuiltinSimprocs/BitVec.lean

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ builtin_dsimproc [simp, seval] reduceSMTSDiv ((smtSDiv _ _ : BitVec _)) := reduc
156156
builtin_dsimproc [simp, seval] reduceGetLsb (getLsbD _ _) := reduceGetBit ``getLsbD getLsbD
157157
/-- Simplification procedure for `getMsb` (most significant bit) on `BitVec`. -/
158158
builtin_dsimproc [simp, seval] reduceGetMsb (getMsbD _ _) := reduceGetBit ``getMsbD getMsbD
159+
/-- Simplification procedure for `clz` (count leading zeros) on `BitVec`. -/
160+
builtin_dsimproc [simp, seval] reduceClz (clz _) := reduceUnary ``BitVec.clz 2 BitVec.clz
159161

160162
/-- Simplification procedure for `getElem` on `BitVec`. -/
161163
builtin_dsimproc [simp, seval] reduceGetElem ((_ : BitVec _)[_]) := fun e => do

src/Std/Tactic/BVDecide/Bitblast/BVExpr/Basic.lean

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ inductive BVUnOp where
139139
Reverse the bits in a bitvector.
140140
-/
141141
| reverse
142+
/--
143+
Count leading zeros.
144+
-/
145+
| clz
142146
deriving Hashable, DecidableEq
143147

144148
namespace BVUnOp
@@ -149,6 +153,7 @@ def toString : BVUnOp → String
149153
| rotateRight n => s!"rotR {n}"
150154
| arithShiftRightConst n => s!">>a {n}"
151155
| reverse => "rev"
156+
| clz => "clz"
152157

153158
instance : ToString BVUnOp := ⟨toString⟩
154159

@@ -161,6 +166,7 @@ def eval : BVUnOp → (BitVec w → BitVec w)
161166
| rotateRight n => (BitVec.rotateRight · n)
162167
| arithShiftRightConst n => (BitVec.sshiftRight · n)
163168
| reverse => BitVec.reverse
169+
| clz => BitVec.clz
164170

165171
@[simp] theorem eval_not : eval .not = ((~~~ ·) : BitVec w → BitVec w) := by rfl
166172

@@ -178,6 +184,8 @@ theorem eval_arithShiftRightConst : eval (arithShiftRightConst n) = (BitVec.sshi
178184

179185
@[simp] theorem eval_reverse : eval .reverse = (BitVec.reverse : BitVec w → BitVec w) := by rfl
180186

187+
@[simp] theorem eval_clz : eval .clz = (BitVec.clz : BitVec w → BitVec w) := by rfl
188+
181189
end BVUnOp
182190

183191
/--

src/Std/Tactic/BVDecide/Bitblast/BVExpr/Circuit/Impl/Expr.lean

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Impl.Operations.Mul
1919
import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Impl.Operations.Udiv
2020
import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Impl.Operations.Umod
2121
import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Impl.Operations.Reverse
22+
import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Impl.Operations.Clz
2223

2324
/-!
2425
This module contains the implementation of a bitblaster for `BitVec` expressions (`BVExpr`).
@@ -224,6 +225,12 @@ where
224225
dsimp only at heaig
225226
omega
226227
⟨⟨res, this⟩, cache.cast (AIG.LawfulVecOperator.le_size (f := bitblast.blastReverse) ..)⟩
228+
| .clz =>
229+
let res := bitblast.blastClz eaig evec
230+
have := by
231+
apply AIG.LawfulVecOperator.le_size_of_le_aig_size (f := bitblast.blastClz)
232+
omega
233+
⟨⟨res, this⟩, cache.cast (AIG.LawfulVecOperator.le_size (f := bitblast.blastClz) ..)⟩
227234
| .append lhs rhs h =>
228235
let ⟨⟨⟨aig, lhs⟩, hlaig⟩, cache⟩ := goCache aig lhs cache
229236
let ⟨⟨⟨aig, rhs⟩, hraig⟩, cache⟩ := goCache aig rhs cache
@@ -335,7 +342,7 @@ theorem go_decl_eq (aig : AIG BVBit) (expr : BVExpr w) (cache : Cache aig) :
335342
· apply Nat.le_trans <;> assumption
336343
· next op expr =>
337344
match op with
338-
| .not | .rotateLeft .. | .rotateRight .. | .arithShiftRightConst .. | .reverse =>
345+
| .not | .rotateLeft .. | .rotateRight .. | .arithShiftRightConst .. | .reverse | .clz =>
339346
rw [AIG.LawfulVecOperator.decl_eq]
340347
rw [goCache_decl_eq]
341348
have := (goCache aig expr cache).result.property
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/-
2+
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Luisa Cicolini, Siddharth Bhat, Henrik Böving
5+
-/
6+
prelude
7+
import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Impl.Const
8+
import Std.Sat.AIG.If
9+
10+
/-!
11+
This module contains the implementation of a bitblaster for `BitVec.clz`.
12+
-/
13+
14+
namespace Std.Tactic.BVDecide
15+
16+
open Std.Sat
17+
18+
variable [Hashable α] [DecidableEq α]
19+
20+
namespace BVExpr
21+
namespace bitblast
22+
23+
def blastClz (aig : AIG α) (x : AIG.RefVec aig w) :
24+
AIG.RefVecEntry α w :=
25+
let wconst := blastConst aig w
26+
go aig x 0 wconst
27+
where
28+
go (aig : AIG α) (x : AIG.RefVec aig w) (curr : Nat) (acc : AIG.RefVec aig w) :=
29+
if hc : curr < w then
30+
let lhs := blastConst aig (w := w) (w - 1 - curr)
31+
let res := AIG.RefVec.ite aig ⟨x.get curr hc, lhs, acc⟩
32+
let aig := res.aig
33+
let acc := res.vec
34+
have := AIG.LawfulVecOperator.le_size (f := AIG.RefVec.ite) ..
35+
let x : AIG.RefVec aig w := x.cast this
36+
go aig x (curr + 1) acc
37+
else
38+
⟨aig, acc⟩
39+
termination_by w - curr
40+
41+
namespace blastClz
42+
43+
end blastClz
44+
45+
theorem blastClz.go_le_size (aig : AIG α) (curr : Nat) (acc : AIG.RefVec aig w)
46+
(xc : AIG.RefVec aig w) :
47+
aig.decls.size ≤ (go aig xc curr acc).aig.decls.size := by
48+
unfold go
49+
dsimp only
50+
split
51+
· refine Nat.le_trans ?_ (by apply go_le_size)
52+
apply AIG.LawfulVecOperator.le_size (f := AIG.RefVec.ite)
53+
· simp
54+
termination_by w - curr
55+
56+
theorem blastClz.go_decl_eq (aig : AIG α) (curr : Nat) (acc : AIG.RefVec aig w)
57+
(xc : AIG.RefVec aig w) :
58+
∀ (idx : Nat) h1 h2,
59+
(go aig xc curr acc).aig.decls[idx]'h1 = aig.decls[idx]'h2 := by
60+
generalize hgo : go aig xc curr acc = res
61+
unfold go at hgo
62+
dsimp only at hgo
63+
split at hgo
64+
· rw [← hgo]
65+
intros
66+
rw [blastClz.go_decl_eq, AIG.LawfulVecOperator.decl_eq (f := AIG.RefVec.ite)]
67+
apply AIG.LawfulVecOperator.lt_size_of_lt_aig_size (f := AIG.RefVec.ite)
68+
assumption
69+
· simp [← hgo]
70+
termination_by w - curr
71+
72+
instance : AIG.LawfulVecOperator α AIG.RefVec blastClz where
73+
le_size := by
74+
intros
75+
unfold blastClz
76+
dsimp only
77+
apply blastClz.go_le_size
78+
decl_eq := by
79+
intros
80+
unfold blastClz
81+
dsimp only
82+
apply blastClz.go_decl_eq
83+
84+
end bitblast
85+
end BVExpr
86+
87+
end Std.Tactic.BVDecide

src/Std/Tactic/BVDecide/Bitblast/BVExpr/Circuit/Lemmas/Expr.lean

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Lemmas.Operations.Mul
2020
import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Lemmas.Operations.Udiv
2121
import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Lemmas.Operations.Umod
2222
import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Lemmas.Operations.Reverse
23+
import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Lemmas.Operations.Clz
2324
import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Impl.Expr
2425

2526
/-!
@@ -425,6 +426,12 @@ theorem go_denote_eq (aig : AIG BVBit) (expr : BVExpr w) (assign : Assignment)
425426
BitVec.getElem_reverse, BitVec.getMsbD_eq_getLsbD, decide_true, Bool.true_and]
426427
rw [goCache_denote_eq]
427428
exact hinv
429+
· rw [← hres]
430+
simp only [eval_un, BVUnOp.eval_clz, BitVec.clz]
431+
rw [denote_blastClz]
432+
intro idx hidx
433+
rw [goCache_denote_eq]
434+
exact hinv
428435
· next h =>
429436
subst h
430437
rw [← hres]
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/-
2+
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Luisa Cicolini, Siddharth Bhat, Henrik Böving
5+
-/
6+
prelude
7+
import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Lemmas.Basic
8+
import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Impl.Operations.Clz
9+
import Std.Tactic.BVDecide.Bitblast.BVExpr.Circuit.Lemmas.Const
10+
11+
/-!
12+
This module contains the verification of the bitblaster for `BitVec.clz` from
13+
`Impl.Operations.Clz`. We prove that the accumulator of the `go` function
14+
at step`n` represents the portion of the `ite` nodes in the AIG constructed for
15+
bits `0` until `n`.
16+
-/
17+
18+
namespace Std.Tactic.BVDecide
19+
20+
open Std.Sat
21+
open Std.Sat.AIG
22+
23+
variable [Hashable α] [DecidableEq α]
24+
25+
namespace BVExpr
26+
27+
namespace bitblast
28+
namespace blastClz
29+
30+
example (x : Nat) (hx : 0 < x) : ∃ y, x = y + 1 := by
31+
exact Nat.exists_eq_add_one.mpr hx
32+
33+
theorem go_denote_eq {w : Nat} (aig : AIG α)
34+
(acc : AIG.RefVec aig w) (xc : AIG.RefVec aig w) (x : BitVec w) (assign : α → Bool)
35+
(hx : ∀ (idx : Nat) (hidx : idx < w), ⟦aig, xc.get idx hidx, assign⟧ = x.getLsbD idx)
36+
(hacc : ∀ (idx : Nat) (hidx : idx < w),
37+
if curr = 0 then ⟦aig, acc.get idx hidx, assign⟧ = (BitVec.ofNat w w).getLsbD idx
38+
else ⟦aig, acc.get idx hidx, assign⟧ = (BitVec.clzAuxRec x (curr - 1)).getLsbD idx)
39+
:
40+
∀ (idx : Nat) (hidx : idx < w),
41+
42+
(go aig xc curr acc).aig,
43+
(go aig xc curr acc).vec.get idx hidx,
44+
assign
45+
46+
=
47+
(BitVec.clzAuxRec x (w - 1)).getLsbD idx := by
48+
intro idx hidx
49+
generalize hgo: go aig xc curr acc = res
50+
unfold go at hgo
51+
split at hgo
52+
· case isTrue h =>
53+
simp only [BitVec.natCast_eq_ofNat, BitVec.ofNat_eq_ofNat] at hgo
54+
rw [← hgo, go_denote_eq]
55+
· intro idx hidx
56+
rw [AIG.LawfulVecOperator.denote_mem_prefix (f := RefVec.ite)]
57+
· simp [hx]
58+
· simp [Ref.hgate]
59+
· intro idx hidx
60+
simp only [Nat.add_eq_zero, Nat.succ_ne_self, and_false, reduceIte, RefVec.get_cast,
61+
Ref.cast_eq, Nat.add_one_sub_one]
62+
rw [RefVec.denote_ite]
63+
split
64+
· next h =>
65+
rw [hx] at h
66+
have : 1 < 2 ^ w := by exact Nat.one_lt_two_pow_iff.mpr (by omega)
67+
rcases curr with _|curr
68+
· simp [denote_blastConst, BitVec.clzAuxRec_zero, h, BitVec.ofNat_sub_ofNat_of_le (x := w) (y := 1) (by omega) (by omega)]
69+
· have := Nat.lt_pow_self (n := curr + 1) (a := 2) (by omega)
70+
have := Nat.pow_lt_pow_of_lt (a := 2) (n := curr + 1) (m := w) (by omega) (by omega)
71+
simp only [denote_blastConst, BitVec.clzAuxRec_succ, h, reduceIte]
72+
rw [BitVec.ofNat_sub_ofNat_of_le (x := w) (y := 1) (by omega) (by omega),
73+
BitVec.ofNat_sub_ofNat_of_le (x := w - 1) (y := curr + 1) (by omega) (by omega)]
74+
· split at hacc
75+
· next h1 h2 =>
76+
have hxc : x.getLsbD 0 = false := by rw [hx] at h1; simp [h2] at h1; exact h1
77+
simp [hacc, h2, BitVec.clzAuxRec_zero, hxc]
78+
· next h1 h2 =>
79+
obtain ⟨curr, hcurr⟩ : ∃ curr', curr = curr' + 1 := by apply Nat.exists_eq_add_one.mpr (by omega)
80+
subst hcurr
81+
have hxc : x.getLsbD (curr + 1) = false := by rw [hx] at h1; simp at h1; exact h1
82+
simp [hacc, BitVec.clzAuxRec_succ, hxc]
83+
· case isFalse h =>
84+
rw [← hgo]
85+
simp only [show ¬curr = 0 by omega, reduceIte] at hacc
86+
simp only [hacc, BitVec.clzAuxRec_eq_clzAuxRec_of_le (x := x) (n := curr - 1) (by omega)]
87+
88+
end blastClz
89+
90+
@[simp]
91+
theorem denote_blastClz (aig : AIG α) (xc : RefVec aig w) (x : BitVec w) (assign : α → Bool)
92+
(hx : ∀ (idx : Nat) (hidx : idx < w), ⟦aig, xc.get idx hidx, assign⟧ = x.getLsbD idx)
93+
:
94+
∀ (idx : Nat) (hidx : idx < w),
95+
⟦(blastClz aig xc).aig, (blastClz aig xc).vec.get idx hidx, assign⟧
96+
=
97+
(BitVec.clzAuxRec x (w - 1)).getLsbD idx := by
98+
intro idx hidx
99+
generalize hb : blastClz aig xc = res
100+
unfold blastClz at hb
101+
dsimp only at hb
102+
rw [← hb, blastClz.go_denote_eq (x := x) (w := w)]
103+
· exact hx
104+
· intro idx hidx
105+
simp only [reduceIte, BitVec.natCast_eq_ofNat]
106+
rw [denote_blastConst]
107+
108+
end bitblast
109+
end BVExpr
110+
111+
end Std.Tactic.BVDecide

0 commit comments

Comments
 (0)