Skip to content

Conversation

@mtzguido
Copy link
Member

This changes the slprop matching procedure in the checker to never backtrack on the result of SMT queries, and instead decide "which path" to take only through syntactic and unification checks. We still allow using the SMT explicitly via rewrites, and also this introduces a notion of matching key to automatically allow the checker to generate SMT guards. As an example

val pts_to ([@@@mkey] r : ref int) (x : int) : slprop

marks the reference as a matching key, and if we ever have pts_to r foo in the context and need to prove pts_to r bar, the matcher will succeed generating a (delayed) guard for pts_to r foo == pts_to r bar, which we will later try to discharge. We also still perform ambiguity checks, so if we also had pts_to r baz in the context, both matches would be "guarded" successes, and we would provide an error explaining the ambiguity:

fn test (r : ref int) (foo bar baz : int)
  requires pts_to r foo ** pts_to r bar
  ensures  pts_to r baz
{
  ();
}
- Cannot prove:
    pts_to r baz
- In the context:
    pts_to r foo ** pts_to r bar
- Some hints:
- Ambiguous match for resource:
    pts_to r baz
- It can be matched by both:
    pts_to r foo
  and:
    pts_to r bar
  in the context.

This impacts heavily on all existing Pulse code. All code in this repo is updated to work, but we should look at external repos.

@mtzguido
Copy link
Member Author

I should add this means we can safely admit queries when checking pulse code, since they don't affect the behavior of the checker. Lax-checking is therefore reenabled.

cases_of_is_tree None l;
unfold is_tree_cases;
intro_is_tree_leaf x;
()
Copy link
Contributor

Choose a reason for hiding this comment

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

I have run into this issue many times where you need to add an extra dummy statement to trigger the matcher. (Sometimes even let x = foo (); x...)

Could we fix this more generally?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's not needed here. I probably added it since when there's a failure in the last statement of a function there's no clear indication of whether it's the precondition that failed, or proving the function's postcondition. I want to fix that soon too, it's very annoying.

I'm not sure if I've seen an instance of what you mention on this branch, if you see it could you post an example?

Copy link
Contributor

Choose a reason for hiding this comment

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

let n = !p;
rewrite each node as n;
rewrite pts_to p n as pts_to (Some?.v tree) n;
// rewrite each ltree as tree.left;
Copy link
Contributor

Choose a reason for hiding this comment

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

Leftover comment.

{
let new_left = insert_avl cmp n.left key;
vl := {data = n.data; left = new_left; right = n.right};
admit();
Copy link
Contributor

Choose a reason for hiding this comment

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

You forgot an admit here.

{
let new_right = insert_avl cmp n.right key;
vl := {data = n.data; left = n.left; right = new_right};
admit();
Copy link
Contributor

Choose a reason for hiding this comment

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

You forgot an admit here.

match tree {
None -> {
is_tree_case_none None;
rewrite is_tree None 'l as is_tree tree 'l;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a common pattern in matches now. Can you do a rewrite each tree to None instead?

Copy link
Member Author

Choose a reason for hiding this comment

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

That rewrite is actually implicitly injected by the checker on a match, and now without the SMT it's kind of working against us. The state at the beginning of the branch is is_tree None ', so we lost the mention of tree, but we anyway need to prove a postcondition mentioning it.

I'm gonna try removing this automatic rewrite and see if things are better. We added it for compactness, but seems to be counterproductive now.

I suppose another possibly much nicer solution would be having the rewrite operate on the postcondition, but so far we don't do much to the postcondition at all.

let unpacked c _v = pts_to c.r #0.5R true


#set-options "--print_implicits"
Copy link
Contributor

Choose a reason for hiding this comment

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

Stray debugging option.

OR.on_range_put i j k #g
}

#set-options "--print_full_names --print_implicits"
Copy link
Contributor

Choose a reason for hiding this comment

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

Stray debugging option.

fold (istar preds');
fold (maybe_holds v q preds');
rewrite (istar preds') as (maybe_holds v q preds');
// fold (maybe_holds v q preds');
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this no longer work?

Copy link
Contributor

Choose a reason for hiding this comment

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

To answer my own question, it's because maybe_holds is a match. And so you need to tell the matcher which branch is true.

}
Cons _ _ -> {
unfold is_list;
admit();
Copy link
Contributor

Choose a reason for hiding this comment

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

You forgot an admit here.

rewrite each r._1 as p31;
rewrite each r._2 as p32;
rewrite each r._3 as pivot;
// ^FIXME: would be nicer to rewrite r as (p31, p32, pivot) but projectors don't unfold
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes indeed. Are you saying that (x, y)._1 doesn't unify with x? @nikswamy ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not without unfolding, and we disable all unfolding during the unification checks performed by the matcher since we found them to go haywire previously. Probably something to revisit now.

I did add special support in Pulse to simplify (x,y)._1 ~> x for tuple2, I think I can extend this for all tuples and make this work, trying.

Copy link
Member Author

Choose a reason for hiding this comment

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

I allowed some unfolding and now this is just let p31, p32, pivot = partition_wrapper a lo hi lb rb;

@mtzguido mtzguido force-pushed the no_smt_backtracking branch 3 times, most recently from 6ebf60f to d8d96bb Compare February 19, 2025 03:18
@mtzguido mtzguido force-pushed the no_smt_backtracking branch from d6f13b8 to 6273aae Compare February 24, 2025 21:18
without that simplification, Rust extraction needs to monomorphize
fst and snd, and that does not work very well.
@mtzguido mtzguido merged commit d6a0e4a into FStarLang:main Feb 25, 2025
1 check passed
@mtzguido mtzguido deleted the no_smt_backtracking branch February 25, 2025 23:13
@mtzguido mtzguido mentioned this pull request Oct 22, 2025
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.

3 participants