Open xldenis opened 3 years ago
Do we really need old
for benchmarks, especially #83?
I'd like to know how we need old
more in detail.
I think a workaround is to use a phantom variable that records a value at a specific point.
We can give PhantomData<T>
the model of T
and define a ghost operation like the following.
#[trusted]
#[ensures(result.model() === a)]
fn record(a: &T) -> PhantomData<T> { PhantomData }
Oh that is clever.
I think it might be sufficient. I quite like this as i can then put off implementing old/labels.
Hmm I've encountered two issues with this so far:
PhantomData
:/record
to take self
not &self
but this would require it to be actually ghost I can imagine the type inference problem (1.), but anyway adding type annotations works.
I wonder what you mean in (2.).
Do you mean taking a: T
instead of a: &T
? That does not work when T
is not Copy
.
Exactly, unless it were truly ghost and thus not subject to borrow checking restrictions.
It's indeed a well-known fact that ghost variables and historical variables have the same expressiveness. That being said, in Creusot, I think we want both in the end. So the question is rather what is simpler for the PLDI submission.
I think that historical variables (i.e., old
and `labels) are subject to fewer design decisions, and are perhaps simpler to implement. However, as you describe, it's unlikely that we will be able to use Why3's support for historical variables.
The implementation idea that comes to my mind is to systematically create historical variables as soon as a variable is created/or a label is crossed (i.e., for each variable x, create a variable old_x), and mutate it when relevant. The number of generated variable is then quadratic (number of variables in the source times numbers of labels). If this is an issue in the generated code, then a simple liveness analysis will do the job of removing the useless variables.
I agree that it's better if we can implement and use true history variables. But anyway the ghost variable hack works (if very hacky) so it's OK for now I think. After all this part does not really affect the paper, I think.
@jhjourdan I actually think that we will be able to fully benefit from why3's old mechanism and labels! it will take a bit of work on the MLCFG side but it should work just as well as for normal why3 code.
That being said, @shiatsumat's idea works for now, and allows us to move forward while we wait for a 'proper' solution.
I was thinking about this again recently, especially now that Pearlite has old
(used in closures for now).
In theory old
works 'out-of-the-box' in MLCFG (with stackify), but the way we translate rust programs breaks that.
In particular, old
always refers to the value of a variable at function entry, but we do something which masks this, for example:
fn my_func(x: &mut T) { ... }
Becomes something like:
let cfg my_func (x : borrowed T) =
var x_1 : borrowed T;
...
{
x_1 <- x
...
goto BB0;
}
In the rest of the program we use x_1
when we're referring to the function parameter x
. Since x_1
technically didn't exist at function entry old
doesn't work on it.
I think a 'simple' fix for this is to add a notion of label
to MLCFG and systematically intro a Entry
label at the start of each function, then translating old(x)
in an invariant
or proof_assert
into x at Entry
instead.
The specifications used in #83 critically depend on having a functioning version of
old
in pearlite. On the one hand this requires adding support for the syntax ofold
but also requires going a bit further and also discussing its semantics.The
old
of why3 refers to the value of a variable at the previous label state or the entry of the function. If the variable doesn't exist at that state then it does nothing. This is a problem for us as it turns out none of our variables exist at entry of the function (at least to why3).For example:
actually is turned into something like
But to why3
a_1
doesn't exist at the start of the function! and it is definitely not equal toa
.I'm not sure what the best solution to this is. I have a few ideas:
old
would work correctly here. However could this lead to counter intuitve behavior?var a_1 : u32 = a;
and add a specific label after all thevar
declarations (aka shift the entry down to the start of the codeold(a_1)
intoold(a)
by detecting thata_1
is meant to refer toa
.