Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
db25a27
Rebased
mira-alford Jul 8, 2026
dd2f616
undoing a debugging change
mira-alford Jul 8, 2026
af430d1
Reduction is working
mira-alford Jul 8, 2026
7e78697
Clean up the graph to prevent errors
mira-alford Jul 8, 2026
7a921f8
cram promotion
mira-alford Jul 8, 2026
94407a5
simplify removed
mira-alford Jul 8, 2026
2fec26d
filter out guards
mira-alford Jul 8, 2026
4e58290
cleanup
mira-alford Jul 21, 2026
e9eaafc
Some more documenting
mira-alford Jul 21, 2026
38dc7a9
smt backend skeleton
mira-alford Jul 21, 2026
aeacc51
Replaced all the old boogie backend stuff with builders
mira-alford Jul 22, 2026
ea8dbba
Summary inlining
mira-alford Jul 24, 2026
5a8875f
Procedure Summaries
mira-alford Jul 24, 2026
de346dc
gitignoring .helix alongside vscode
mira-alford Jul 24, 2026
446de02
Messy, inlining summaries was not wise
mira-alford Jul 24, 2026
d35bde9
Checking VCs properly
mira-alford Jul 27, 2026
45ac348
Small cleanup
mira-alford Jul 27, 2026
f0da82d
smt backend test
mira-alford Jul 27, 2026
9dbb12d
smt backend test
mira-alford Jul 27, 2026
fdfbf33
rebased
mira-alford Jul 27, 2026
6723b42
Summary inlining is useful now
mira-alford Jul 27, 2026
909b423
Documentation
mira-alford Jul 27, 2026
a116da0
More documentation
mira-alford Jul 27, 2026
3f819cd
cram promote
mira-alford Jul 27, 2026
b7797fc
formatter
mira-alford Jul 27, 2026
c04449b
bug
mira-alford Jul 27, 2026
42f24a9
Removed useless comment
mira-alford Jul 27, 2026
68d58c6
test depends on cvc5
mira-alford Jul 27, 2026
ba49f76
rename, primes bad
mira-alford Jul 27, 2026
38bb495
quantifier-free removed by default
mira-alford Jul 27, 2026
d3ec54a
confusing work in progress acyclification
mira-alford Jul 30, 2026
589ffd4
Cleanup + assumes
mira-alford Jul 31, 2026
a3c2101
transform assertions in acyclic
mira-alford Jul 31, 2026
a9887c3
promotions
mira-alford Jul 31, 2026
5c715f6
renamed invariant to acyclic
mira-alford Jul 31, 2026
f2b6afe
Proper ordering of statements
mira-alford Jul 31, 2026
612b1c4
promotions, loop removal test
mira-alford Jul 31, 2026
1626c8d
fixed smt type hints with bandaid, needs rework to functorize over co…
mira-alford Jul 31, 2026
8593e1d
IfThen intrinsic
mira-alford Jul 31, 2026
968dba1
ITE Expression cleanup
mira-alford Jul 31, 2026
ef59d49
Formatter
mira-alford Jul 31, 2026
e9140aa
Live running backend
mira-alford Aug 3, 2026
d870eeb
Better reporting and variable cleanup
mira-alford Aug 3, 2026
1f7293a
cleanup debug print
mira-alford Aug 3, 2026
475db6d
formatter
mira-alford Aug 3, 2026
4c61976
idk
mira-alford Aug 4, 2026
5bc171d
boogie type fix
mira-alford Aug 4, 2026
a468307
disambiguating variables, removing duplicate types
mira-alford Aug 7, 2026
a2b78a1
Minimal variable disambiguation
mira-alford Aug 10, 2026
8b22614
Triggers support
mira-alford Aug 10, 2026
5945b6b
fixed missing id check
mira-alford Aug 10, 2026
5a53292
test promotion
mira-alford Aug 10, 2026
1a5613e
restored ssa to skip maps/observable
mira-alford Aug 10, 2026
895852f
test for live smt backend
mira-alford Aug 10, 2026
4b4d65c
Simpler live smt test
mira-alford Aug 10, 2026
31768af
promote test
mira-alford Aug 10, 2026
2c1cbde
Improements from PR review
mira-alford Aug 10, 2026
91e22ea
Formatter
mira-alford Aug 11, 2026
3986f25
Unused variables removed
mira-alford Aug 11, 2026
326fc94
Moved ITE to its own intrin
mira-alford Aug 11, 2026
0e8d9c7
Moved ITE to its own intrin
mira-alford Aug 11, 2026
613e04c
formatter
mira-alford Aug 11, 2026
c5c06b4
updated docstring
mira-alford Aug 11, 2026
903c258
tree sitter ite promotion
mira-alford Aug 11, 2026
95c9042
Cleaned up SMT backend using effects, slightly broken
mira-alford Aug 31, 2026
216f633
Declaration dependency graph pt1
mira-alford Aug 31, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ trace.json
**/*.dot

.vscode/
.helix/
.direnv
.envrc
11 changes: 11 additions & 0 deletions bin/main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,17 @@ let repl ~verb ~echo_cmd =
|> Iter.flat_map complete_filename
|> Iter.map (fun s -> l ^ " " ^ s)
|> Iter.iter (LNoise.add_completion completions)
| Ok (`Atom "dump-smt" :: fnames as l)
| Ok (`Atom "live-smt" :: fnames as l) ->
let c = last fnames in
let l =
List.take (List.length l - opt_len c) l
|> List.to_string ~sep:" " CCSexp.to_string
in
(match c with Some n -> Iter.singleton n | None -> Iter.empty)
|> Iter.flat_map complete_filename
|> Iter.map (fun s -> l ^ " " ^ s)
|> Iter.iter (LNoise.add_completion completions)
| Ok (`Atom "run-transforms" :: transforms as l)
| Ok (`Atom "run-transform" :: transforms as l) ->
let c = last transforms in
Expand Down
3 changes: 2 additions & 1 deletion lib/analysis/sva.ml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ module SVAAbstraction = struct
match op with
| (`BVADD | `BVOR | `BVXOR | `BVAND | `BVMUL) as op ->
(eval_binary op a b rt, rt)
| `OR | `AND | `Cases | `MapUpdate -> (SymAddrSetLattice.top, rt)
| `OR | `AND | `Cases | `MapUpdate | `IfThenElse ->
(SymAddrSetLattice.top, rt)
| `BVConcat ->
( SymAddrSetLattice.fold
(fun sb1 vs1 acc ->
Expand Down
15 changes: 13 additions & 2 deletions lib/backends/dune
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
(library
(public_name bincaml.backends)
(name backends)
(modules boogie)
(libraries containers containers-data containers.pp lang transforms))
(modules boogie smt)
(libraries containers containers-data containers.pp lang transforms)
(preprocess
(pps
ppx_here
ppx_deriving.show
ppx_deriving.eq
ppx_deriving.ord
ppx_deriving.map
ppx_deriving.iter
ppx_deriving.fold
ppx_deriving.enum
ppx_expect)))
238 changes: 238 additions & 0 deletions lib/backends/smt.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
open Lang
open Lang.Common
open Bincaml_util.Common
open Bincaml_util
open Expr_smt
open Expr

(** SMT Backend. This backend expects the CFA Reduction and Summary Inlining
transforms. Will output a .smt file containing all declarations and asserts
necessary for verification. Live variant runs smt solver in bincaml,
printing more useful and readable output from analysis. *)

open Effect

type _ Effect.t +=
| Push : SMTLib2.builder -> unit Effect.t
| Verify : SMTLib2.builder * (Program.proc * Program.stmt) -> unit Effect.t

(* Get any ambiguous variables (shared name, different type). *)
let ambiguities (program : Program.t) : VarSet.t Iter.t =
Program.procs program
(* Get all variables in program: *)
|> Iter.flat_map
(snd %> Procedure.iter_blocks
%> Iter.flat_map (fun (_, b) ->
Iter.append (Block.read_vars_iter b) (Block.assigned_vars_iter b)))
(* Group variables by name: *)
|> Iter.group_by
~hash:(fun v -> Hash.string @@ Var.name v)
~eq:(fun v1 v2 -> String.equal (Var.name v1) (Var.name v2))
|> Iter.map VarSet.of_list
(* Only keep lists of length > 1: *)
|> Iter.filter (VarSet.cardinal %> ( <= ) 2)

(* Map rvars to sexps, necessary to handle ambiguities and
function calls which are sensitive to context in program. *)
let rvar_map (program : Program.t) =
ambiguities program
(* Map all ambiguous variables to as expressions. *)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I may be misunderstanding but is this to deal with locals that share names with different types in different procedures? If so could you explain briefly in a comment please.

Is it dealing with local variables across different procedures? IMO it would be safer to always mangle local variables with the procedure name, coincident names that are not defined locally in different procedures would constitute an malformed program (I believe there is a check for this but I may be wrong). Sharing definitions of local variables with the same name across different procedures seems to risk funny semantics- creating invalid constraints between locals of different procedures.

For verifying a single procedure at a time, I think you could define locals after the (push) and they will be discarded after the (pop); but without mangling this could still restrict how easy it is to share subgoals between different procedure checks (it requires you pop before analysing another procedure).

Kait suggested it might be to do with inlining definitinos of types where variables are only annotated with their name; in that case it doesn't seem safe to include all local variables in the one map; they might just have different types in different procedrues?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It is dealing with variables across procedures as all declarations need to happen globally (cannot be inside a push/pop scope). It is quite an annoying limitation but the smtlib spec suggests (when generating smt code) to just disambiguate types using as expressions and so long as all asserts are inside scopes (this is the case) procedures should never interfere. I am a fan of the renaming idea if you think that would be better though, I'm not the biggest fan of this approach.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If the constraints themselves are scoped then there's no problem. Its not obvious whether mangling variables is going to be more or less convoluted, so up to you if you want to implement it that way.

|> Iter.flat_map
@@ VarSet.to_iter
%> Iter.map (fun v ->
let sexp =
fun s ->
let var, s = SMTLib2.get_var v s in
let typ = fst @@ SMTLib2.of_typ (Var.typ v) in
(CCSexp.(list [ atom "as"; var; typ ]), s)
in
(v, sexp))
|> VarMap.of_iter

(** Remove all variable declarations from a builder which conflict with a sort
declaration. *)
let dedup_decls (builder : SMTLib2.builder) : SMTLib2.builder =
let removals =
VarMap.to_iter builder.var_decls
|> Iter.filter_map (fun (var, _) ->
match Var.typ var with
| Sort (name, _) -> Some (Var.copy ~typ:(Types.Variable name) var)
| _ -> None)
|> VarSet.of_iter
in

{
builder with
var_decls =
builder.var_decls
|> VarMap.filter (fun var _ -> not @@ VarSet.mem var removals);
}

let visit_stmt procedure rvars = function
| Stmt.Instr_Assert { body } as stmt ->
(* Verify negation of assertion is unsat. *)
let builder =
SMTLib2.add_assert
(SMTLib2.of_bexpr ~rvars (BasilExpr.boolnot body))
SMTLib2.empty
|> snd
in
perform (Verify (builder, (procedure, stmt)));

(* Assert the actual assertion. *)
let smt = SMTLib2.of_bexpr ~rvars body in
let builder = SMTLib2.add_assert smt SMTLib2.empty |> snd in
perform (Push builder)
| Stmt.Instr_Assume { body } ->
(* Assert the assumption as is. SSA makes this equiv to assume. *)
let smt = SMTLib2.of_bexpr ~rvars body in
let builder = SMTLib2.add_assert smt SMTLib2.empty |> snd in
perform (Push builder)
| Stmt.Instr_Assign { al } ->
(* Assign is just an assertion of equivalent lhs and rhs.
This is bidirectional, but SSA + Reachability conds avoid this
causing issues. *)
let asserts =
List.map
(fun (v, e) ->
BasilExpr.binexp ~op:`EQ (BasilExpr.rvar v) e
|> SMTLib2.of_bexpr ~rvars)
al
in
let builder =
List.fold_left
(fun builder smt -> SMTLib2.add_assert smt builder |> snd)
SMTLib2.empty asserts
in
perform (Push builder)
| _ -> ()

let visit_procedure ~rvars (program : Program.t) (procedure : Program.proc) =
print_endline @@ "visitng proc" ^ (Procedure.id procedure |> ID.name);
let builder =
SMTLib2.empty |> SMTLib2.push |> snd
|> SMTLib2.echo ("Verifying Procedure: " ^ ID.name (Procedure.id procedure))
|> snd
in
perform (Push builder);

let local_decls = Procedure.local_decls procedure in
local_decls
|> Hashtbl.iter (fun k v ->
perform (Push (snd @@ SMTLib2.decl_var v SMTLib2.empty)));

(* Translate each statement to smt. *)
Procedure.iter_stmt_topo_fwd procedure
|> flip Iter.for_each (visit_stmt procedure rvars);

perform (Push (SMTLib2.pop SMTLib2.empty |> snd))

let visit_program (program : Program.t) =
let program =
(Transforms.Ssa.set_params ~skip_observable:false ~skip_maps:false) program
in
let rvars = rvar_map program in
Program.declarations program
|> Iter.map snd
|> flip Iter.for_each (function
| Program.Procedure { definition } ->
visit_procedure ~rvars program definition
| other -> perform (Push (SMTLib2.trans_decl other SMTLib2.empty |> snd)))

(** Offline SMT backend. Converts entire program to smt and dumps to chan.
Inserts verification condition checks with echos for easier tracing. *)
let smt_offline chan (program : Program.t) : unit =
let open Containers_pp in
let builder = ref SMTLib2.empty in
(try visit_program program with
| effect Push b, k ->
builder := SMTLib2.append !builder b;
Effect.Deep.continue k ()
| effect Verify (b, c), k ->
builder := snd @@ SMTLib2.push !builder;
builder := SMTLib2.append !builder b;
builder := snd @@ SMTLib2.check_sat !builder;
builder := snd @@ SMTLib2.pop !builder;
Effect.Deep.continue k ());
let p =
Expr_smt.SMTLib2.to_sexp ~set_logic:true !builder
|> Iter.map (Sexp.to_string %> text)
|> Iter.to_list |> append_nl
in
flush chan;
let fmt = Format.formatter_of_out_channel chan in
Containers_pp.Pretty.to_format ~width:80 fmt p;
Format.flush fmt ()

(** Online SMT backend. Starts up a solver and feeds program one statement at a
time to it. Prints more useful messages for failing VCs and tracks stats for
entire procedures. *)
let smt_online chan (program : Program.t) : unit =
let module M = Map.Make (struct
type t = Smt.Solver.result [@@deriving eq, ord]
end) in
flush chan;
let solver =
Bincaml_util.Smt.Solver.create
{
Bincaml_util.Smt.Config.cvc5 with
log = Bincaml_util.Smt.Config.printf_log;
}
in
let results : int M.t IDMap.t ref = ref IDMap.empty in
(try visit_program program with
| effect Push b, k ->
if
SMTLib2.to_sexp ~set_logic:false b
|> Iter.map (Smt.Solver.add_sexp solver)
|> Iter.for_all (function
| `List (`Atom "error" :: body) as s ->
Printf.fprintf chan "solver error: %s" (CCSexp.to_string s);
false
| _ -> true)
then Effect.Deep.continue k ()
| effect Verify (b, (proc, stmt)), k ->
Smt.Solver.push solver;
SMTLib2.commands_to_sexp b
|> Iter.map (Smt.Solver.add_sexp solver)
|> Iter.iter (const ());
let result = Smt.Solver.check solver in
(match result with
| Unknown ->
Printf.fprintf chan "\nUnknown Assertion:\n%s\n"
(Stmt.to_string Var.pretty Var.pretty BasilExpr.pretty stmt)
| Sat -> (
Printf.fprintf chan "\nFailing Assertion: %s\n"
(Stmt.to_string Var.pretty Var.pretty BasilExpr.pretty stmt);
Printf.fprintf chan "Belonging to procedure: %s\n"
(ID.name @@ Procedure.id proc);
Printf.fprintf chan "Counterexample:\n";
let model = Smt.Solver.get_model solver in
match model with
| `Atom a -> Printf.fprintf chan "%s\n" (Sexp.to_string model)
| `List l ->
l |> List.to_iter
|> Iter.filter (function
| `List (`Atom "define-fun" :: `Atom var :: _ :: `Atom typ :: _)
->
Procedure.lookup_local_decl proc var |> Option.is_some
|| Program.get_decl_by_name var program |> Option.is_some
| _ -> false)
|> flip Iter.for_each (fun s ->
Printf.fprintf chan "%s\n" (Sexp.to_string s)))
| Unsat -> ());
results :=
IDMap.update (Procedure.id proc)
Option.(
or_ ~else_:(Some M.empty)
%> map (M.update result (or_ ~else_:(Some 0) %> map (( + ) 1))))
!results;
Smt.Solver.pop solver;
Effect.Deep.continue k ());
Smt.Solver.stop solver;
flip IDMap.iter !results (fun id map ->
Printf.fprintf chan "Procedure %s verified with:\n" (ID.name id);
[ Unknown; Sat; Unsat ]
|> List.iter (fun k ->
M.get_or ~default:0 k map
|> Printf.fprintf chan "\t %s: %d\n" (Smt.Solver.show_result k)))
1 change: 1 addition & 0 deletions lib/fe/AbsBasilIR.ml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/fe/BasilIR.cf
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ rules BVLogicalBinOp ::= "bvule" | "bvugt" | "bvuge" | "bvult" | "bvslt" | "b

rules IntBinOp ::= "intadd" | "intmul" | "intsub" | "intdiv" | "intmod" ;
rules IntLogicalBinOp ::= "intlt" | "intle" | "intgt" | "intge" ;
rules IntrinOp ::= "booland" | "boolor" | "bvand" | "bvor" | "bvadd" | "bvxor" | "bvconcat" | "bvmul" | "update";
rules IntrinOp ::= "booland" | "boolor" | "bvand" | "bvor" | "bvadd" | "bvxor" | "bvconcat" | "bvmul" | "update" | "ite";
rules PointerBinOp ::= "ptradd" ;

{- SPECIFICATION -}
Expand Down
4 changes: 2 additions & 2 deletions lib/fe/LexBasilIR.mll

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion lib/fe/ParBasilIR.mly

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/fe/PrintBasilIR.ml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/fe/ShowBasilIR.ml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/fe/SkelBasilIR.ml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading