-
Notifications
You must be signed in to change notification settings - Fork 4
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
annotations: calculus annotations (wp,ert,wlp) #7
Conversation
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 your PR! I left some comments about simplifying the code.
src/calculi/mod.rs
Outdated
@@ -0,0 +1,121 @@ | |||
mod ert; |
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 add a module comment in this file outlining what the module does (introducing the calculus annotations and that they check which proof rules can be used where).
src/calculi/mod.rs
Outdated
pub trait Calculus: fmt::Debug { | ||
fn name(&self) -> Ident; | ||
|
||
fn check_encoding(&self, encoding: EncodingKind, direction: Direction) -> Result<(), ()>; |
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.
Instead of returning ()
in case of error, return a singleton error type, e.g. CalculusAnnotationError
that implements std::fmt::Error
(you can do this via the thiserror
crate we're using).
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.
Also it would be good to add a comment describing what this method is supposed to do.
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.
Result<(),()> is used as a boolean success flag in this context. The returned error is wrapped with a proper AnnotationError::CalculusEncodingMismatch
by the visitor. Should I use the singleton error type in this case?
src/calculi/mod.rs
Outdated
fn check_encoding(&self, encoding: EncodingKind, direction: Direction) -> Result<(), ()>; | ||
} | ||
|
||
pub struct CalculusVisitor<'tcx> { |
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 add a comment describing shortly what this visitor does.
src/calculi/mod.rs
Outdated
{ | ||
if let Some(direction) = self.direction { | ||
if calculus | ||
.check_encoding(encoding_ref.clone(), direction) |
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.
Why do you need to clone encoding_ref
? Why doesn't check_encoding
simply accept a reference?
src/calculi/wlp.rs
Outdated
@@ -0,0 +1,44 @@ | |||
use std::fmt; |
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.
I feel like these implementation files require surprisingly lots of boilerplate. Why not have one struct type for a calculus annotation that maintains the Ident
and an enum Calculus
(with values Wp
, Wlp
, etc.)? Then you have one (or multiple) functions that simply match on the Calculus
value for the visitor. I don't see the need to pass these abstract dyn Trait
objects around if the set of possible calculi is always known by Caesar and not extensible.
src/calculi/wlp.rs
Outdated
|
||
fn check_encoding(&self, encoding: EncodingKind, direction: Direction) -> Result<(), ()> { | ||
match encoding { | ||
EncodingKind::Invariant(_) | EncodingKind::KInduction(_) | EncodingKind::Unroll(_) => { |
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.
These match statements could also be simplified. Instead of nesting every possible case and returning Ok or Err, simply match on the encoding kind and then return the expected direction from the match statement (or immediately abort for Ast, Past, Ost). Then, you can raise an Err if the direction does not match.
src/proof_rules/induction.rs
Outdated
@@ -26,7 +26,8 @@ use super::{Encoding, EncodingEnvironment, EncodingGenerated}; | |||
use super::util::*; | |||
|
|||
/// Syntactic sugar encoding for K-Induction encodings of type k=1 | |||
pub struct InvariantAnnotation(AnnotationInfo); | |||
#[derive(Clone)] |
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.
I don't see the reason why these types should be cloneable.
src/proof_rules/induction.rs
Outdated
@@ -26,7 +26,8 @@ use super::{Encoding, EncodingEnvironment, EncodingGenerated}; | |||
use super::util::*; | |||
|
|||
/// Syntactic sugar encoding for K-Induction encodings of type k=1 | |||
pub struct InvariantAnnotation(AnnotationInfo); | |||
#[derive(Clone)] | |||
pub struct InvariantAnnotation(pub AnnotationInfo); |
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.
Why is this AnnotationInfo
pub
?
src/proof_rules/mod.rs
Outdated
Ast(ASTAnnotation), | ||
} | ||
|
||
impl EncodingKind { |
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.
I feel these impl
s are pretty verbose we'll need to think of a better approach to architect the types here. A very simple alternative would be to simply add a method to the Encoding
trait called check_calculus
that receives a simple value of a new enum type Calculus
and a direction and then returns Ok(())
or an Err(EncodingCalculusError)
. Then the checks wouldn't be part of a specific calculus, but rather of the encoding.
AnnotationError::UnknownAnnotation(span, anno_name ) => { | ||
Diagnostic::new(ReportKind::Error, span) | ||
.with_message(format!( | ||
"The '{}' annotation is unknown.", |
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.
Does this error occur from the user? Or is it always a compiler error? If the latter is true, then we should simply panic instead of emitting an error if the user can't fix it.
@@ -210,6 +210,7 @@ pub struct ProcDecl { | |||
/// (read) access to the proc declaration. | |||
pub body: RefCell<Option<Block>>, | |||
pub span: Span, | |||
pub calculus: Option<Ident>, |
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.
Why would this be an Ident
if the calculus is always from a fixed set of values that are built into the compiler? Couldn't you save a value of an enum type Calculus
here?
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! I left a number of minor comments.
src/intrinsic/annotations.rs
Outdated
#[derive(Debug, Clone)] | ||
|
||
pub enum CalculusType { | ||
WP, |
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.
The enum items should be named Wp
, Wlp
, Ert
src/proof_rules/induction.rs
Outdated
@@ -192,6 +207,18 @@ impl Encoding for KIndAnnotation { | |||
resolve.visit_expr(invariant) | |||
} | |||
|
|||
fn check_calculus(&self, calculus: &Calculus, direction: Direction) -> Result<(), ()> { |
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 method should return a descriptive error type, e.g. just an AnnotationError
} | ||
|
||
Ok(()) | ||
} |
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.
There should be an empty line after an fn
src/proof_rules/induction.rs
Outdated
@@ -81,6 +84,19 @@ impl Encoding for InvariantAnnotation { | |||
resolve.visit_expr(invariant) | |||
} | |||
|
|||
fn check_calculus(&self, calculus: &Calculus, direction: Direction) -> Result<(), ()> { | |||
if direction |
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.
The inline match expression inside an if statement should be rewritten to just a single match statement that either returns an Err
or Ok
without the if then else.
src/proof_rules/mciver_ast.rs
Outdated
@@ -115,6 +117,15 @@ impl Encoding for ASTAnnotation { | |||
check_annotation_call(tycheck, call_span, &self.0, args)?; | |||
Ok(()) | |||
} | |||
fn check_calculus(&self, calculus: &Calculus, direction: Direction) -> Result<(), ()> { | |||
if let CalculusType::WP = calculus.calculus_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.
Why not just if calculus.calculus_type == CalculusType::Wp && direction == Direction::Down
?
src/proof_rules/past.rs
Outdated
@@ -85,6 +87,16 @@ impl Encoding for PASTAnnotation { | |||
resolve.visit_expr(k) | |||
} | |||
|
|||
fn check_calculus(&self, calculus: &Calculus, direction: Direction) -> Result<(), ()> { | |||
if let CalculusType::ERT = calculus.calculus_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.
Rewrite into single if as described above.
} | ||
|
||
Ok(()) | ||
} |
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.
Empty line after fn
.
src/proof_rules/util.rs
Outdated
] | ||
} | ||
|
||
/// Encode the extend step in bmc recursively for k times |
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.
Colon after end of sentence and empty line before heading.
src/proof_rules/util.rs
Outdated
let builder = ExprBuilder::new(span); | ||
let extrem_lit = match direction { |
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.
extreme_lit
} | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct Calculus { |
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.
I don't quite understand why this struct exists. Shouldn't the CalculusType
enum be sufficient? Why have the additional Ident
?
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! This is looking very good. I'm traveling right now, but I'll create some documentation for this and then merge it in the next couple of days! :)
|
||
Err(()) | ||
fn is_calculus_allowed(&self, calculus: &Calculus, direction: Direction) -> bool { | ||
matches!(calculus.calculus_type, CalculusType::Wp) && direction == Direction::Down |
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.
Just a side note, if you derive PartialEq
and Eq
for the CalculusType
enum, then you can use normal ==
to compare these values.
src/proof_rules/induction.rs
Outdated
@@ -207,17 +202,17 @@ impl Encoding for KIndAnnotation { | |||
resolve.visit_expr(invariant) | |||
} | |||
|
|||
fn check_calculus(&self, calculus: &Calculus, direction: Direction) -> Result<(), ()> { | |||
fn is_calculus_allowed(&self, calculus: &Calculus, direction: Direction) -> bool { | |||
if direction |
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.
The logic here seems a bit convoluted. Why not simply immediately return direction == match calculus.calculus_type { CalculusType::Wp | CalculusType::Ert => Direction::Up, CalculusType::Wlp => Direction::Down, }
?
AnnotationError::CalculusEncodingMismatch(span, calculus_name, encoding_name ) => { | ||
Diagnostic::new(ReportKind::Error, span) | ||
.with_message(format!( | ||
"The '{}' calculus does not support the '{}' encoding.", |
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.
Maybe also include the direction? E.g. using @invariant
with @wlp
in a coproc
causes confusion because the diagnostic "The 'wlp' calculus does not support the 'invariant' encoding." makes it look like the invariant-rule cannot be used for wlp at all.
Implemented the system to add calculus annotations and implemented three main calculi:
wp
wlp
ert
These annotations can be used only on top of procedures at the moment in the following format:
Calculus annotations allow or reject certain proof rule encodings depending on the calculi that the proof rule argue about.
For example
k-induction
is a proof rule that proves an upper bound on thewp
of the program. Therefore usingk-induction
in aproc
(lower bound reasoning) is invalid. If theproc
is annotated with@wp
, Caesar will reject the file and throw an error. On the other hand,coproc
(upper bound reasoning) with@wp
allows@k_induction(...)
annotations inside the procedure.Technical:
To implement a new calculus annotation, one must create a new struct for the annotation in the
src/calculi
directory and this struct should implement theCalculus
trait, which requires a simplename
function and acheck_encoding
function that returns aResult<(),()>
based on the given encoding annotation and direction. Afterward the corresponding declaration of the annotation must happen in theinit_calculi
function.