1- import Lean.Meta.Tactic.BVDecide
1+ module
2+
3+ public import Lean.Expr
4+ public import Lean.Meta.Basic
5+ public import Lean.Meta.Tactic.BVDecide.Counterexample
6+ public import Std.Tactic.BVDecide.Bitblast.BVExpr.Basic
7+ public import Std.Tactic.BVDecide.Syntax
8+ import all Lean.Meta.Tactic.BVDecide.Counterexample
9+
210
311open Lean
12+ open Std.Tactic.BVDecide
13+
14+ /--
15+ Render a `width`-bit bitvector with the given numeric `value` as an SMT-LIB
16+ binary literal (e.g. `#b00000001`).
17+ -/
18+ public def formatBitVecLiteral (value : Nat) (width : Nat) : String := Id.run do
19+ let mut s := " #b"
20+ for i in [0 :width] do
21+ let bit := (value >>> (width - 1 - i)) &&& 1
22+ s := s.push (if bit == 1 then '1' else '0' )
23+ return s
24+
25+ /--
26+ Render a Lean `Name` as an SMT-LIB symbol, quoting it with `|...|` if it is not
27+ a valid simple symbol.
28+ -/
29+ public def formatSmtSymbol (n : Name) : String :=
30+ -- `smtSymbolToName` builds every symbol as a single-component name holding the
31+ -- exact string, so we read it back directly rather than relying on
32+ -- `Name.toString`'s escaping/pseudo-syntax handling.
33+ let s := match n with
34+ | .mkSimple s => s
35+ | _ => n.toString (escape := false)
36+ let isSimpleChar c := Char.isAlphanum c || " ~!@$%^&*_-+=<>.?/" .contains c
37+ let needsQuote :=
38+ match s.toList with
39+ | [] => true
40+ | c :: cs => c.isDigit || !(c :: cs).all isSimpleChar
41+ if needsQuote then " |" ++ s ++ " |" else s
42+
43+ /--
44+ Unfold a chain of `let`-bound sort aliases (introduced by `define-sort`) to the
45+ underlying sort.
46+ -/
47+ private partial def resolveSortAlias (e : Expr) : MetaM Expr := do
48+ match e with
49+ | .fvar fvarId =>
50+ match ← fvarId.getDecl with
51+ | .ldecl (value := v) .. => resolveSortAlias v
52+ | _ => return e
53+ | _ => return e
454
5- private partial def getIntrosSize (e : Expr) : Nat :=
6- go 0 e
55+ /--
56+ Print a model for a satisfiable query following SMT-LIB `get-model` semantics.
57+
58+ `fvars` are the free variables corresponding to the SMT-LIB declared constants
59+ (in declaration order) and `equations` is the (possibly partial) assignment
60+ found by `bv_decide`. Booleans are reflected as one-bit bitvectors, so a boolean
61+ constant `b` shows up in `equations` as `BitVec.ofBool b`. Since `bv_decide`'s
62+ counterexample is not guaranteed to assign every declared constant, any constant
63+ missing from `equations` is completed with all-zeros for bitvectors and `false`
64+ for booleans.
65+
66+ Must be run in a context in which `fvars` are valid (e.g. inside the goal's
67+ `withContext`).
68+ -/
69+ public def printModel (fvars : Array FVarId) (equations : Array (Expr × BVExpr.PackedBitVec)) :
70+ MetaM Unit := do
71+ -- Index the values found by `bv_decide` by the free variable they assign.
72+ -- Bitvector constants appear directly as free variables, whereas boolean
73+ -- constants `b` appear wrapped as `BitVec.ofBool b` with a one-bit value.
74+ let mut values : Std.HashMap FVarId Nat := {}
75+ for (e, pv) in equations do
76+ if e.isFVar then
77+ values := values.insert e.fvarId! pv.bv.toNat
78+ else if let .app (.const ``BitVec .ofBool []) x := e then
79+ if x.isFVar then
80+ values := values.insert x.fvarId! pv.bv.toNat
81+ let mut lines : Array String := #[]
82+ for fvar in fvars do
83+ let decl ← fvar.getDecl
84+ let sym := formatSmtSymbol decl.userName
85+ -- A declared constant may have a `define-sort` alias as its type, in which
86+ -- case it is a `let`-bound free variable that we unfold to its definition.
87+ match ← resolveSortAlias decl.type with
88+ | .const ``Bool [] =>
89+ -- A missing assignment is completed with `false`.
90+ let value := values.getD fvar 0 == 1
91+ lines := lines.push s!" (define-fun {sym} () Bool {value})"
92+ | .app (.const ``BitVec []) we =>
93+ let some w := we.nat? | continue
94+ let value := values.getD fvar 0
95+ lines := lines.push
96+ s!" (define-fun {sym} () (_ BitVec {w}) {formatBitVecLiteral value w})"
97+ | _ =>
98+ -- Skip declarations outside the supported QF_BV fragment (e.g. functions
99+ -- with arguments), which cannot appear in a counterexample anyway.
100+ continue
101+ let model :=
102+ if lines.isEmpty then " (\n )"
103+ else " (\n " ++ String.intercalate " \n " lines.toList ++ " \n )"
104+ logInfo model
105+
106+ open Lean.Meta.Tactic.BVDecide in
107+ /--
108+ Report the outcome of a satisfiable query from a `bv_decide` counterexample.
109+
110+ If the counterexample is genuine, print `sat` (and, when `getModel` is set, the
111+ model) and return exit code `0`. If it is *spurious* -- i.e. `bv_decide`
112+ abstracted an unsupported subterm as an opaque variable, or did not use a
113+ relevant hypothesis, so the assignment may not actually satisfy the problem --
114+ report it as an error and return exit code `1`, mirroring `bvDecide`.
115+ -/
116+ public def reportCounterExample (fvars : Array FVarId) (getModel : Bool)
117+ (counterExample : CounterExample) : MetaM UInt8 := do
118+ let diagnosis ← DiagnosisM.run DiagnosisM.diagnose counterExample
119+ if diagnosis.uninterpretedSymbols.isEmpty && diagnosis.unusedRelevantHypotheses.isEmpty then
120+ logInfo " sat"
121+ if getModel then
122+ printModel fvars counterExample.equations
123+ return (0 : UInt8)
124+ else
125+ logError (← addMessageContextFull (← explainCounterExampleQuality counterExample))
126+ return (1 : UInt8)
127+
128+ /--
129+ Count the leading `let` binders (introduced by `define-sort`) followed by the
130+ `forall` binders (introduced by `declare-fun`/`declare-const`) of `e`. Traversal
131+ stops at the first `let` following the foralls, which corresponds to the
132+ `define-fun`/`define-const` bindings of the body. Returns the number of leading
133+ `let`s and the number of following `forall`s, respectively.
134+ -/
135+ private partial def getIntrosSize (e : Expr) : Nat × Nat :=
136+ goLets 0 e
7137where
8- go (size : Nat) : Expr → Nat
9- | .forallE _ _ b _ => go (size + 1 ) b
10- | .mdata _ b => go size b
11- | _ => size
138+ goLets (lets : Nat) : Expr → Nat × Nat
139+ | .letE _ _ _ b _ => goLets (lets + 1 ) b
140+ | .mdata _ b => goLets lets b
141+ | e => (lets, goForalls 0 e)
142+ goForalls (foralls : Nat) : Expr → Nat
143+ | .forallE _ _ b _ => goForalls (foralls + 1 ) b
144+ | .mdata _ b => goForalls foralls b
145+ | _ => foralls
12146
13147/--
14- Introduce only forall binders and preserve names.
148+ Introduce the leading `define-sort` `let` binders together with the
149+ `declare-fun`/`declare-const` `forall` binders, preserving names. Returns the
150+ free variables corresponding to the declared symbols, i.e. those coming from the
151+ `forall` binders only (the introduced sort definitions are excluded).
15152-/
16- def _root_.Lean.MVarId.introsP (mvarId : MVarId) : MetaM (Array FVarId × MVarId) := do
153+ public def _root_.Lean.MVarId.introsP (mvarId : MVarId) : MetaM (Array FVarId × MVarId) := do
17154 let type ← mvarId.getType
18155 let type ← instantiateMVars type
19- let n := getIntrosSize type
20- if n == 0 then
156+ let (numLets, numForalls) := getIntrosSize type
157+ if numLets + numForalls == 0 then
21158 return (#[], mvarId)
22159 else
23- mvarId.introNP n
160+ let (fvars, mvarId) ← mvarId.introNP (numLets + numForalls)
161+ -- Drop the leading sort definitions; keep only the declared symbols.
162+ return (fvars.extract numLets fvars.size, mvarId)
24163
25164open Meta.Tactic.BVDecide in
26- structure Context where
165+ public structure Context where
27166 acNf : Bool
28167 parseOnly : Bool
29168 timeout : Nat
@@ -34,15 +173,15 @@ structure Context where
34173 disableKernel : Bool
35174 solverMode : Elab.Tactic.BVDecide.SolverMode
36175
37- abbrev SolverM := ReaderT Context MetaM
176+ public abbrev SolverM := ReaderT Context MetaM
38177
39178namespace SolverM
40179
41- def getParseOnly : SolverM Bool := return (← read).parseOnly
42- def getInput : SolverM String := return (← read).input
43- def getKernelDisabled : SolverM Bool := return (← read).disableKernel
180+ public def getParseOnly : SolverM Bool := return (← read).parseOnly
181+ public def getInput : SolverM String := return (← read).input
182+ public def getKernelDisabled : SolverM Bool := return (← read).disableKernel
44183
45- def getBVDecideConfig : SolverM Elab.Tactic.BVDecide.BVDecideConfig := do
184+ public def getBVDecideConfig : SolverM Elab.Tactic.BVDecide.BVDecideConfig := do
46185 let ctx ← read
47186 return {
48187 timeout := ctx.timeout
@@ -56,7 +195,7 @@ def getBVDecideConfig : SolverM Elab.Tactic.BVDecide.BVDecideConfig := do
56195 solverMode := ctx.solverMode
57196 }
58197
59- def run (x : SolverM α) (ctx : Context) (coreContext : Core.Context) (coreState : Core.State) :
198+ public def run (x : SolverM α) (ctx : Context) (coreContext : Core.Context) (coreState : Core.State) :
60199 IO α := do
61200 let (res, _, _) ← ReaderT.run x ctx |> (Meta.MetaM.toIO · coreContext coreState)
62201 return res
0 commit comments