Skip to content

Commit 7706b87

Browse files
authored
feat: bv_decide support for structures of supported types (#6724)
This PR adds support for `bv_decide` to automatically split up non-recursive structures that contain information about supported types. It can be controlled using the new `structures` field in the `bv_decide` config.
1 parent 9b74c07 commit 7706b87

File tree

8 files changed

+202
-11
lines changed

8 files changed

+202
-11
lines changed

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Lean.Elab.Tactic.BVDecide.Frontend.Normalize.Rewrite
1111
import Lean.Elab.Tactic.BVDecide.Frontend.Normalize.AndFlatten
1212
import Lean.Elab.Tactic.BVDecide.Frontend.Normalize.EmbeddedConstraint
1313
import Lean.Elab.Tactic.BVDecide.Frontend.Normalize.AC
14+
import Lean.Elab.Tactic.BVDecide.Frontend.Normalize.Structures
1415

1516
/-!
1617
This module contains the implementation of `bv_normalize`, the preprocessing tactic for `bv_decide`.
@@ -43,9 +44,17 @@ def bvNormalize (g : MVarId) (cfg : BVDecideConfig) : MetaM (Option MVarId) := d
4344
(go g).run cfg g
4445
where
4546
go (g : MVarId) : PreProcessM (Option MVarId) := do
46-
let some g ← g.falseOrByContra | return none
47+
let some g' ← g.falseOrByContra | return none
48+
let mut g := g'
4749

4850
trace[Meta.Tactic.bv] m!"Running preprocessing pipeline on:\n{g}"
51+
let cfg ← PreProcessM.getConfig
52+
53+
if cfg.structures then
54+
let some g' ← structuresPass.run g | return none
55+
g := g'
56+
57+
trace[Meta.Tactic.bv] m!"Running fixpoint pipeline on:\n{g}"
4958
let pipeline ← passPipeline
5059
Pass.fixpointPipeline pipeline g
5160

src/Lean/Elab/Tactic/BVDecide/Frontend/Normalize/AndFlatten.lean

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ partial def andFlatteningPass : Pass where
4343
where
4444
processGoal (goal : MVarId) : StateRefT AndFlattenState MetaM Unit := do
4545
goal.withContext do
46-
let hyps ← goal.getNondepPropHyps
46+
let hyps ← getPropHyps
4747
hyps.forM processFVar
4848

4949
processFVar (fvar : FVarId) : StateRefT AndFlattenState MetaM Unit := do

src/Lean/Elab/Tactic/BVDecide/Frontend/Normalize/Basic.lean

+1-3
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,14 @@ def getConfig : PreProcessM BVDecideConfig := read
3232
@[inline]
3333
def checkRewritten (fvar : FVarId) : PreProcessM Bool := do
3434
let val := (← get).rewriteCache.contains fvar
35-
trace[Meta.Tactic.bv] m!"{mkFVar fvar} was already rewritten? {val}"
3635
return val
3736

3837
@[inline]
3938
def rewriteFinished (fvar : FVarId) : PreProcessM Unit := do
40-
trace[Meta.Tactic.bv] m!"Adding {mkFVar fvar} to the rewritten set"
4139
modify (fun s => { s with rewriteCache := s.rewriteCache.insert fvar })
4240

4341
def run (cfg : BVDecideConfig) (goal : MVarId) (x : PreProcessM α) : MetaM α := do
44-
let hyps ← goal.getNondepPropHyps
42+
let hyps ← goal.withContext do getPropHyps
4543
ReaderT.run x cfg |>.run' { rewriteCache := Std.HashSet.empty hyps.size }
4644

4745
end PreProcessM

src/Lean/Elab/Tactic/BVDecide/Frontend/Normalize/EmbeddedConstraint.lean

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def embeddedConstraintPass : Pass where
2727
name := `embeddedConstraintSubsitution
2828
run' goal := do
2929
goal.withContext do
30-
let hyps ← goal.getNondepPropHyps
30+
let hyps ← getPropHyps
3131
let mut relevantHyps : SimpTheoremsArray := #[]
3232
let mut seen : Std.HashSet Expr := {}
3333
let mut duplicates : Array FVarId := #[]
@@ -49,11 +49,12 @@ def embeddedConstraintPass : Pass where
4949
return goal
5050

5151
let cfg ← PreProcessM.getConfig
52+
let targets ← goal.withContext getPropHyps
5253
let simpCtx ← Simp.mkContext
5354
(config := { failIfUnchanged := false, maxSteps := cfg.maxSteps })
5455
(simpTheorems := relevantHyps)
5556
(congrTheorems := (← getSimpCongrTheorems))
56-
let ⟨result?, _⟩ ← simpGoal goal (ctx := simpCtx) (fvarIdsToSimp := ← goal.getNondepPropHyps)
57+
let ⟨result?, _⟩ ← simpGoal goal (ctx := simpCtx) (fvarIdsToSimp := targets)
5758
let some (_, newGoal) := result? | return none
5859
return newGoal
5960

src/Lean/Elab/Tactic/BVDecide/Frontend/Normalize/Rewrite.lean

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ def rewriteRulesPass : Pass where
4646

4747
let some (_, newGoal) := result? | return none
4848
newGoal.withContext do
49-
(← newGoal.getNondepPropHyps).forM PreProcessM.rewriteFinished
49+
(← getPropHyps).forM PreProcessM.rewriteFinished
5050
return newGoal
5151
where
5252
getHyps (goal : MVarId) : PreProcessM (Array FVarId) := do
5353
goal.withContext do
54-
let mut hyps ← goal.getNondepPropHyps
54+
let hyps ← getPropHyps
5555
let filter hyp := do
5656
return !(← PreProcessM.checkRewritten hyp)
5757
hyps.filterM filter
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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: Henrik Böving
5+
-/
6+
prelude
7+
import Lean.Elab.Tactic.BVDecide.Frontend.Normalize.Basic
8+
import Lean.Meta.Tactic.Cases
9+
10+
/-!
11+
This module contains the implementation of the pre processing pass for automatically splitting up
12+
structures containing information about supported types into individual parts recursively.
13+
14+
The implementation runs cases recursively on all "interesting" types where a type is interesting if
15+
it is a non recursive structure and at least one of the following conditions hold:
16+
- it contains something of type `BitVec`/`UIntX`/`Bool`
17+
- it is parametrized by an interesting type
18+
- it contains another interesting type
19+
-/
20+
21+
namespace Lean.Elab.Tactic.BVDecide
22+
namespace Frontend.Normalize
23+
24+
open Lean.Meta
25+
26+
/--
27+
Contains a cache for interesting and uninteresting types such that we don't duplicate work in the
28+
structures pass.
29+
-/
30+
structure InterestingStructures where
31+
interesting : Std.HashMap Name Bool := {}
32+
33+
private abbrev M := StateRefT InterestingStructures MetaM
34+
35+
namespace M
36+
37+
@[inline]
38+
def lookup (n : Name) : M (Option Bool) := do
39+
let s ← get
40+
return s.interesting.get? n
41+
42+
@[inline]
43+
def markInteresting (n : Name) : M Unit := do
44+
modify (fun s => {s with interesting := s.interesting.insert n true})
45+
46+
@[inline]
47+
def markUninteresting (n : Name) : M Unit := do
48+
modify (fun s => {s with interesting := s.interesting.insert n false})
49+
50+
end M
51+
52+
partial def structuresPass : Pass where
53+
name := `structures
54+
run' goal := do
55+
let (_, { interesting, .. }) ← checkContext goal |>.run {}
56+
57+
let goals ← goal.casesRec fun decl => do
58+
if decl.isLet || decl.isImplementationDetail then
59+
return false
60+
else
61+
let some const := decl.type.getAppFn.constName? | return false
62+
return interesting.getD const false
63+
match goals with
64+
| [goal] => return goal
65+
| _ => throwError "structures preprocessor generated more than 1 goal"
66+
where
67+
checkContext (goal : MVarId) : M Unit := do
68+
goal.withContext do
69+
for decl in ← getLCtx do
70+
if !decl.isLet && !decl.isImplementationDetail then
71+
discard <| typeInteresting decl.type
72+
73+
constInterestingCached (n : Name) : M Bool := do
74+
if let some cached ← M.lookup n then
75+
return cached
76+
77+
let interesting ← constInteresting n
78+
if interesting then
79+
M.markInteresting n
80+
return true
81+
else
82+
M.markUninteresting n
83+
return false
84+
85+
constInteresting (n : Name) : M Bool := do
86+
let env ← getEnv
87+
if !isStructure env n then
88+
return false
89+
let constInfo := (← getConstInfoInduct n)
90+
if constInfo.isRec then
91+
return false
92+
93+
let ctorTyp := (← getConstInfoCtor constInfo.ctors.head!).type
94+
let analyzer state arg := do
95+
return state || (← typeInteresting (← arg.fvarId!.getType))
96+
forallTelescope ctorTyp fun args _ => args.foldlM (init := false) analyzer
97+
98+
typeInteresting (expr : Expr) : M Bool := do
99+
match_expr expr with
100+
| BitVec n => return (← getNatValue? n).isSome
101+
| UInt8 => return true
102+
| UInt16 => return true
103+
| UInt32 => return true
104+
| UInt64 => return true
105+
| USize => return true
106+
| Bool => return true
107+
| _ =>
108+
let some const := expr.getAppFn.constName? | return false
109+
constInterestingCached const
110+
111+
112+
end Frontend.Normalize
113+
end Lean.Elab.Tactic.BVDecide

src/Std/Tactic/BVDecide/Syntax.lean

+8-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ structure BVDecideConfig where
3939
-/
4040
embeddedConstraintSubst : Bool := true
4141
/--
42+
Split up local declarations of structures that are collections of other supported types into their
43+
individual parts automatically.
44+
-/
45+
structures : Bool := true
46+
/--
4247
Output the AIG of bv_decide as graphviz into a file called aig.gv in the working directory of the
4348
Lean process.
4449
-/
@@ -67,8 +72,9 @@ syntax (name := bvCheck) "bv_check " optConfig str : tactic
6772

6873
/--
6974
Close fixed-width `BitVec` and `Bool` goals by obtaining a proof from an external SAT solver and
70-
verifying it inside Lean. The solvable goals are currently limited to the Lean equivalent of
71-
[`QF_BV`](https://smt-lib.org/logics-all.shtml#QF_BV):
75+
verifying it inside Lean. The solvable goals are currently limited to
76+
- the Lean equivalent of [`QF_BV`](https://smt-lib.org/logics-all.shtml#QF_BV)
77+
- automatically splitting up `structure`s that contain information about `BitVec` or `Bool`
7278
```lean
7379
example : ∀ (a b : BitVec 64), (a &&& b) + (a ^^^ b) = a ||| b := by
7480
intros

tests/lean/run/bv_structures.lean

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import Std.Tactic.BVDecide
2+
3+
namespace Ex1
4+
5+
structure Basic where
6+
x : BitVec 32
7+
y : BitVec 32
8+
9+
example (b : Basic) : b.x + b.y = b.y + b.x := by
10+
bv_decide
11+
12+
end Ex1
13+
14+
namespace Ex2
15+
16+
structure Basic where
17+
x : BitVec 32
18+
y : BitVec 32
19+
h : y + x = 0
20+
21+
example (b : Basic) : b.x + b.y = 0 := by
22+
bv_decide
23+
24+
end Ex2
25+
26+
namespace Ex3
27+
28+
structure Recursive where
29+
x : BitVec 32
30+
foo : Recursive
31+
32+
-- Don't get fooled by recursive structures
33+
example (_r : Recursive) (a b : BitVec 8) : a + b = b + a := by
34+
bv_decide
35+
36+
end Ex3
37+
38+
namespace Ex4
39+
40+
structure Foo where
41+
x : BitVec 32
42+
y : BitVec 32
43+
h : x + y = 0
44+
45+
structure Bar extends Foo where
46+
z : BitVec 32
47+
48+
structure FooBar extends Bar where
49+
a : Unit
50+
51+
example (f : FooBar) (h : f.z > 0) : f.x + f.y + f.z > 0 := by
52+
bv_decide
53+
54+
end Ex4
55+
56+
namespace Ex5
57+
58+
structure Param (x : BitVec 32) (y : BitVec 32) where
59+
h : y + x = 0
60+
61+
example (x y : BitVec 32) (p : Param x y) : x + y = 0 := by
62+
bv_decide
63+
64+
end Ex5

0 commit comments

Comments
 (0)