Extensions to/limitations of the current ghost state API
sauclovian-g opened this issue · comments
Currently you can declare ghost variables with declare_ghost_state
and assert things about their values with llvm_ghost_value
, mir_ghost_value
, etc.
This creates the following limitation: you cannot update the ghost state explicitly, nor can you fetch it and assert things about it other than equality to some other value.
As an example of why you might want to update a ghost variable, suppose you have some functions that you both wish to verify and then use as overrides for verifying higher-level code. Suppose furthermore that these functions are a low-level implementation of a higher-level interface, and you wish to provide ghost variables to track the higher-level state so the callers do not need to be exposed to the implementation. Currently you cannot do this: as part of the postcondition of one of these functions you can assert that it updates its low-level state correctly and that this corresponds to the intended new high-level state. But there's no way to assign the new high-level state into the ghost variable. (All you can do call llvm_ghost_value
to assert that the new high-level state is equal to the original high-level state, and that's not what you want and also always going to fail.)
Asserting the new high-level state in the postcondition works if the spec is purely used as an override, which is probably why this hasn't come up before.
Meanwhile, you might want to fetch a ghost variable and assert something other than equality about it in any number of cases related to inexact specs: for example, if you're counting events that happen in the C code and it's ok for there to be extras, and the count's tracked in a ghost variable, the postcondition you want is ghost_value >= n
. You might be able to build a model such that you can test for equality, but only at the cost of significant extra effort and probably a bunch of silly contortions.
Suggestion:
- Rename
llvm_ghost_value
and friends tollvm_assert_ghost_value
to make it clearer what they do. - Add
llvm_update_ghost_value
et al. - Also add
llvm_get_ghost_value
et al.
Somewhat silly example for update:
First, some C code:
static unsigned *words;
struct unsigned nwords;
void bignum_add(unsigned x) {
unsigned i;
words[0] += x;
if (words[0] < x) {
i = 1;
while (1) {
if (i == the_nwords) {
bignum_embiggen();
}
words[i] += 1;
if (words[i] != 0) {
break;
}
}
}
}
And now, a candidate spec:
// declare ghost state for tracking the actual value
bignum_val <- declare_ghost_state "bignum_val";
let bignum_add_spec = do {
// get a fresh var for the tracking value and bind it
val <- llvm_fresh_cryptol_var {{ Integer }};
llvm_ghost_value bignum_val val;
// get vars for the internal state and bind them
words_array <- llvm_fresh_value( /* something-or-other */ );
words_var <- llvm_fresh_value( /* something-or-other */ );
llvm_points_to(words_var, words_array);
nwords_var <- llvm_fresh_value( /* something-or-other */ );
llvm_precond(words_var, llvm_find_var("words")); /* or something */
llvm_precond(nwords_var, llvm_find_var("nwords")); /* or something */
// get a var for the argument
x_var <- llvm_fresh_value( /* something-or-other */ );
// run it
llvm_execute_func(x);
// compute updated state
let words_array' = {{ whatever }};
let nwords' = {{ whatever }};
llvm_points_to(words_var, words_array');
llvm_postcond(nwords_var, llvm_find_var("nwords")); /* or something */
// update the tracking state
let val' = {{ val + x_var }};
// assert it matches
assert {{ val' == cryptol_get_value words_array' }};
// update the tracking value (except...)
llvm_ghost_value bignum_val {{ val + x_var }};
}
Then we call
llvm_verify [] bignum_add_spec ...;
(All of this including the code is doubtless wrong in many places; that's not the point.)
The call at the end needs to be llvm_update_ghost_value
or the verification will not succeed.
Note that while there's not really any need for llvm_assert_ghost_value
given llvm_get_ghost_value
, there's a reason it's the only operator we currently have: it's the common case. So it seems worth keeping, even if we deprecate the old spelling.