Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
MLCFG has served us quite well until now, but time time has come for us to consider replacements. In particular, MLCFG has one essential limitation, forced upon us by Why3, which is that all functions must be opaque. This is an issue because often for small trivial functions the contract can be as long or longer than the function itself.
Closures suffer acutely from this: the closure is often no more than a single expression while its contract requires at least a precondition and a postcondition.
Fixing this in WhyML is complex, it goes against the current design of the VC, almost any fix would be a hack. Luckily, Andreï, Jean-Christophe and Paul have been working on an alternative in the form of Coma.
Coma is a new IVL with a few interesting design choices:
This means we can control where abstraction barriers occur in our programs!
It even opens the door to partially transparent functions.
pre
andpost
operators which reify the preconditions and postconditions of handlers as predicates. This is again useful for closures as it allows us to provide the correct definitions for thepostcondition
andprecondition
functions.By adopting Coma in Creusot, we can solve our issues with closures, provide the developers of Coma a good initial usecase, and potentially future proof ourselves to changes in Why3.
In this PR
This PR implements a complete port of Creusot to Coma, in some cases I think this simplifies understanding of the code generation, in others it renders it more complex.
There is two essential limitations to consider in Coma: 1. A black box must be inserted to break all cycles between handlers. This is understandable, as a cycle corresponds to something like a loop.
2. Effect inference is handled lexically. If we're not careful, we can lose framing information about mutable variables. Handler definition blocks define a single scope, and thus if a single handler modifies a variable, other sibling handlers will have to consider that variable as potentially modified.
This is only a problem when dealing with loops though as for acyclic control flow, the final VC will "inline" the real value for that variable.
Taking these limitation into consideration, we obtain the following high-level design:
a. The loop head is surrounded in a black box and guarded by invariants.
a. Within a basic block, we translate each statement to a handler as well. (Purely aesthetic / cleanliness)
To aid in the translation between direct and continuation-passing styles, we make use of an
IntermediateStmt
type.The
IntermediateStmt
is a 'atomic' (to coma) effectful operation, which is sequenced together.Once we have finished translating some (set) of MIR constructs, we can take the sequence of generated
IntermediateStmt
s and fold them into a single CPS expression.In Rust this is much more ergonomic than passing around some boxed continuation closure.
In Coma, only handlers are effectful, while in MLCFG many 'expressions' could be as well, which we were leveraging to encode places.
When a place asserts a constructor (
X as Some
), we would encode it as alet Some(_) = X
, which why3 would then prove was always true.These expressions could be used in pure contexts in
mlcfg
, but this does not work in coma.Instead, we have to use a handler asserting that the passed in value has a specific constructor and producing its component fields.
Thus
X as Some
becomessome X (fun _0 -> ... )
As places are often quite long, this is the primary source of unreadability in Coma code.
Some effort has been made to improve legibility and prevent right-wards drift of expressions, but even then it remains hard to read.
Limitations
While the PR implements a complete backend, we do have a few notable regressions.
variant
, these will have to be manually encoded, we will need to see how we can possibly recover why3's structurally recursive variants.Future work
Besides rectifying the limitations there are some improvements we can still make:
He defines a
.
operator which works much like Haskell's$
.Without it,
((X as Some).0 as Some).0
becomes:This is easily unreadable and causes huge nesting.
Using the Coma dot:
let%attr
syntax which will allow us to de-duplicate our usage of spans which will dramatically improve the legibility of coma expressions and our diffs in tests.let%attr
lets us give an attribute a name, which can then be used via[%@ name]