Skip to content

Smt backend - #223

Open
mira-alford wants to merge 64 commits into
mainfrom
smt-backend
Open

Smt backend#223
mira-alford wants to merge 64 commits into
mainfrom
smt-backend

Conversation

@mira-alford

Copy link
Copy Markdown
Collaborator

Part 1 of the smt backend, yay!

Adds a summary inlining transform which replaces all procedure summaries with asserts/assumes.

Uses this in the dump-smt command, which outputs an smt file that can currently verify basic IL programs (pure and only using primitive types). Support for fancy things like datatypes/matching are not done yet. Should be structured to check that all assertions/ensures are satisfied.

Also makes some changes to the existing smt infrastructure. Fixes some bugs with incorrect symbols for bitvector operations, and makes a mess getting ITEs working (there has to be a better way??).

Comment thread lib/lang/expr_smt.ml Outdated
@katrinafyi
katrinafyi removed their request for review July 27, 2026 02:02
@mira-alford

Copy link
Copy Markdown
Collaborator Author

Oops, should have predicted that failure 💀
Will fix.

Comment thread lib/lang/expr_smt.ml Outdated

@katrinafyi katrinafyi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Pretty cool!

Comment thread lib/backends/smt.ml Outdated
Comment thread lib/lang/algsimp.ml Outdated
Comment thread lib/lang/algsimp.ml
Comment thread lib/lang/ops.ml Outdated
Comment thread lib/transforms/cfa_reduction.ml Outdated
Comment thread lib/transforms/summary_inlining.ml Outdated
Comment thread lib/invariants.ml
Comment thread bin/main.ml Outdated

@ncough ncough left a comment

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'm not quite done reading through yet, but in the interest of not holding anything up, here are some things I have found. I think most are me just not knowing bincaml :)

Comment thread lib/lang/expr_smt.ml
| `INTADD -> atom "+"
| `INTMUL -> atom "*"
| `INTSUB -> atom "-"
| `INTDIV -> atom "/"

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.

Probably should be div, / is for reals.

They should be order invariant thanks to SSA,
however the added dependence on the termination
condition requires them after it's declared. *)
CCVector.append_list final_edge assert_stmts;

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 am just glancing through, so maybe completely wrong. Is it safe here to reorder asserts and assumes? Or are assumes fixed to entry?

type_err "non-equal ite branches : %s %s" (Types.to_string arg1)
(Types.to_string arg2);
]
| [ Types.Boolean; arg1 ] -> []

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'm probably missing something, but this case doesn't make a lot of sense to me. What does the single argument case return if the condition is false?

Comment thread lib/lang/ops.ml

type intrin = [ `Cases (** choose first argument that is defined *) ]
type intrin =
[ `Cases | `IfThenElse (** choose first argument that is defined *) ]

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.

Is the comment on the wrong variant?

let invariants =
Block.fold_forwards ~phi:const
~f:(fun acc stmt ->
match stmt with Stmt.Instr_Assert { body } -> body :: acc | _ -> acc)

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.

Will this include asserts after a statement? Not sure that's right.


(* Add havoc statements. *)
let havocs =
Block.free_vars header_block

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.

Is this the variables mentioned in the block, or the live variables across it? Should be the latter. I guess SSA should guarantee these are the same, but does this pass assume SSA?

in

(* Update header with new statements. *)
let header_block = Block.append_stmts header_block (havocs @ assumes) in

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.

This places havoc and assume after loop header statements? I'd assume they go just after the asserts.

@agle agle left a comment

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.

Nick's already looked at the loop stuff so I'll just comment on this part

Comment thread lib/backends/smt.ml
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?

let proc = Procedure.set_entry_block proc id in
Procedure.PG.map_graph
(fun g ->
Procedure.G.add_edge g (Procedure.Vert.End id) Procedure.Vert.Return)

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 think an intermediate type would be simpler, rather than proc with flattened graph e.g. {reduced_proc: Stmt.t list, reach_end_pred: Expr.t list;}), the caller can combine with the procedure id or copy of the procedure if needed.

in
`Left (Stmt.Instr_Assert { body; attrib })
| other -> `Right other)
in

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.

lgtm

Comment thread lib/backends/smt.ml
(* Produce a list of builders for a procedure, with context.
Builder for local declarations + one for each statement.
Assertions produce a second builder for verification. *)
let build_procedure ~rvars (program : Program.t) (procedure : Program.proc) :

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.

This could maybe be more efficient by passing a iter callback function ((builder * ctx) -> unit) which you call instead of CCVector.push. This means you could construct an Iter.t from this function to pass each intermediate check to the solver.


(* Add invariants to a backedge, subbing phi node vars. *)
let add_invariants =
let phis =

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.

Again, probably confused, but I would have thought you'd want to substitute phis based on what this edge is going to do. Here it seems to be substituting for header self loops?

in
List.fold_left (transform_loop prog) proc loops

let transform (prog : Program.t) =

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.

Just wanted to comment that I find this implementation quite clean and elegant. Doing something rather intricate in ~100 lines.

match loop with
| { block; loop = PrimaryHeader { primary_header; headers; nodes } } ->
let entries = ProcIntra.compute_entries proc loop in
let backedges = ProcIntra.compute_backedges proc loop in

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.

Is there any issue with calling these after altering proc? Maybe given a nested loop or something?

in

(* Add ensures to return block. *)
match List.head_opt return_id with

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.

Is there an invariant ensuring at most one of these?

Stmt.Instr_Assume
{
attrib = StringMap.empty;
body = subst_var lhs @@ subst_expr args e;

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.

A staged subst like this has caught me before. Can you have something like: callee(x): y ensures y = x + 1, invoked as z := callee(y)? Ends up with assume (z = z + 1).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants