-
Notifications
You must be signed in to change notification settings - Fork 274
Parsing and type-checking loop variants (aka ranking functions) #6183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #6183 +/- ##
===========================================
- Coverage 75.39% 74.88% -0.51%
===========================================
Files 1456 1454 -2
Lines 161080 160816 -264
===========================================
- Hits 121440 120435 -1005
- Misses 39640 40381 +741
Continue to review full report at Codecov.
|
487973b
to
08060b4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good apart from a few small things. Please can we have some tests and some documentation!
src/util/std_types.h
Outdated
/// The int type. | ||
/// This is necessary for the type checking of loop variants, which should have | ||
/// some kind of integer type. | ||
class int_typet:public typet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See
cbmc/src/util/mathematical_types.h
Line 20 in 0ae6010
/// Unbounded, signed integers (mathematical integers, not bitvectors) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you so much for your suggestion! Ideally, I want to use a "bounded" int type (as opposed to an unbounded int type) because every loop variant/ranking function must be bounded below. Although ID_signed_int
is used in CBMC's code base, it doesn't seem to be a valid type of GOTO programs. That is, the function implicit_typecast_followed
in
cbmc/src/ansi-c/c_typecast.cpp
Line 489 in 0ae6010
void c_typecastt::implicit_typecast_followed( |
ID_signed_int
as the target of type-casting. Please let me know if you have a suggestion!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In CPROVER, Integer generally means mathematically bounded integer and C int
is a ID_signedbv
or a ID_unsignedbv
. The choice between the two has significant impact in terms of decidability and performance.
Do you need to fix the type of this annotation? I am /guessing/ what you are going to do is take the expression dec(x,y,z,...)
and generate two assertions : dec(x,y,z,...) < dec(__CPROVER_old(x), __CPROVER_old(y), ... )
and 0 <= dec(x,y,z,...)
? If so then you can do this with any ordered type that has a 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for your swift response! As you remarked, one idea is to bound loop variants from below; i.e. 0 <= dec(x,y,z,...)
. Another idea is to use an integer type that is already bounded below (e.g. ID_signedbv
and ID_unsignedbv
). I was initially considering the first idea, and then switched to the second idea. However, maybe the first idea is better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By the way, I finished implementing a prototype of loop variants. If I use the type integer_typet
from
cbmc/src/util/mathematical_types.h
Line 20 in 0ae6010
/// Unbounded, signed integers (mathematical integers, not bitvectors) |
goto-cc toy.c -o toy.goto
and goto-instrument --enforce-all-contracts toy.goto toy.2.goto
. However, cbmc toy.2.goto
fails - it returns an error message of UNIMPLEMENTED. Instead, if I use ID_signedbv
, cbmc toy.2.goto
successfully runs. So I probably should switch from integer_typet
to a different integer type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a decision procedures point of view, bit-vectors (ID_signedbv
, ID_unsignedbv
, ID_bv
) and mathematical integers (ID_integer
) are very very different things. For a fixed bit-width, bit-vector decision problems are in NP. There are even some sub-theories that are in P. Mathematical integers are in undecidable in the general case and NP in the restricted linear case. The decision procedures you use for them are very different and converting between them in an expression is generally very expensive because it is hard for the decision procedures to exchange information in a useful way (because they work so very differently). The native back-end does not support more than trivial reasoning over mathematical integers.
My suggestion would be to avoid fixing which you are using unless you really have to. What you are doing should work just fine for both. If you just have it as an expression then the user can give a ranking function in ID_signedbv
, ID_integer
, ID_float
, whatever. You only have to block the case of ID_real
or add something like 2*(dec(x'',y'',z'') - dec(x',y',z')) < (dec(x',y',z') - dec(x,y,z))
where each '
denotes an previous iteration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is feasible to specify/construct ranking functions using bitvector types. 2LS does that, for instance. However, you need to be careful to use sufficiently wide types so as to avoid any overflows in the arithmetic occurring in the expressions involved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the pointers, Martin, Peter. For now we implemented implicit_typecast_arithmetic
check on the ranking function. This check is quite liberal and avoids fixing the expected type for the ranking function.
In the next PR, when we instrument the code to generate the monotonic decrement assertions from ranking functions, we would carefully explore the various corner cases that you have pointed out.
src/ansi-c/scanner.l
Outdated
@@ -1281,6 +1281,7 @@ __decltype { if(PARSER.cpp98 && | |||
{CPROVER_PREFIX}"finally" { loc(); return TOK_CPROVER_FINALLY; } | |||
{CPROVER_PREFIX}"ID" { loc(); return TOK_CPROVER_ID; } | |||
{CPROVER_PREFIX}"loop_invariant" { loc(); return TOK_CPROVER_LOOP_INVARIANT; } | |||
{CPROVER_PREFIX}"loop_variant" { loc(); return TOK_CPROVER_LOOP_VARIANT; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you committed to the name? My concern is that if we have loop_invariant
and loop_variant
it is natural to think that they are duals / inverses which is not really the intuition we want. Might it be better to call them ranking functions or similar?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this is a good point.
There are several options (in addition to loop_variant
and ranking_function
) here. For example, Dafny calls it decreases
(and I think ACSL too). Coq calls it measures
.
I personally think __CPROVER_decreases
would be the clearest.
What do you think?
cc: @kroening @tautschnig
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the syntax of loop variants, I've swithced from __CPROVER_loop_variant(...)
to __CPROVER_decreases(...)
. If we change our mind later, we can easily change the syntax.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some initial comments.
As Martin suggested, let's add some regression tests. I'll take a closer look again later.
92402a9
to
e788e47
Compare
8d06b14
to
c4b2bbb
Compare
src/util/std_types.h
Outdated
/// The int type. | ||
/// This is necessary for the type checking of loop variants, which should have | ||
/// some kind of integer type. | ||
class int_typet:public typet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the pointers, Martin, Peter. For now we implemented implicit_typecast_arithmetic
check on the ranking function. This check is quite liberal and avoids fixing the expected type for the ranking function.
In the next PR, when we instrument the code to generate the monotonic decrement assertions from ranking functions, we would carefully explore the various corner cases that you have pointed out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approving with a couple of nitpicks.
c4b2bbb
to
cd5decf
Compare
Hi @martin-cs, Could you please take another look at this PR? |
int N = 10; | ||
while (i != N) | ||
__CPROVER_loop_invariant(0 <= i && i <= N) | ||
__CPROVER_decreases(int x = 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to have a __CPROVER_decreases
without a loop invariant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is possible. The default loop invariant is true
. The code for that is already in my local repository, and I will file a second PR soon.
^main.c: In function 'main':$ | ||
^main.c:7:1: error: syntax error before ','$ | ||
^ __CPROVER_decreases\(N - i, 42\)$ | ||
^PARSING ERROR$ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice to see some negative tests!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, great work!
{ | ||
exprt &constraint = static_cast<exprt &>(code.add(spec)); | ||
exprt &invariant = static_cast<exprt &>(code.add(ID_C_spec_loop_invariant)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anything else we could check at this point? For instance, the number of operands? @SaswatPadhi
// this comes with 1 operand, which is a Boolean statement
if(code.operands().size()!=1)
{
error().source_location = code.source_location();
error() << "decreases expected to have 1 operand" << eom;
throw 0;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good idea, but that's already taken care of by the parsing rule, so we don't need to add it to the type checker.
For instance, take a look at regression/contracts/variant_parsing_tuple_fail
regression test added in this PR.
Also note that in future this restriction would be relaxed to allow multi-dimensional ranking functions. I'm not sure if we'll make __CPROVER_decreases
variadic, or allow some other syntax for tuples.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Please fix the
test.desc
files (which are failing on Windows) - Fix the formatting of
main.c
files.
a0c108c
to
d933897
Compare
d933897
to
521bfa5
Compare
521bfa5
to
72f9431
Compare
In this pull request, I have implemented the parsing and type-checking of loop variants (also known as ranking functions). A loop variant is an "integer expression" that (i) is bounded below and (ii) strictly decreases after each iteration of a loop. We use loop variants to prove termination of loops. This is the feature I am currently working on during my research internship at AWS.
Methods or procedures I have added are documented, following the guidelines provided in CODING_STANDARD.md.My commit message includes data points confirming performance improvements (if claimed).White-space or formatting changes outside the feature-related changed lines are in commits of their own.