Skip to content
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

Partial cleanup in solver code #1810

Closed

Conversation

romainbrenguier
Copy link
Contributor

This regroup several commits that do only small refactoring changes in the solver module.
The goal is to improve clarity and use the same conventions as elsewhere (for instance use invariant
instead of assert or throw).

Copy link
Contributor

@LAJW LAJW left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are all great changes, I cannot approve them enough.

@romainbrenguier romainbrenguier force-pushed the refactor/prop_conv branch 3 times, most recently from f200f2b to b0ead02 Compare February 8, 2018 12:19
Copy link
Collaborator

@tautschnig tautschnig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the changes are great, some need review as indicated. (A common theme: please don't remove blank lines that serve as conceptual separators. Wedon'tpaysomeper-linepriceandwhitespaceisreallyusefultohumanreaders.)

The request-for-changes is for a bit that I haven't seen any comment on: what are the performance differences?

bool use_cache;
bool equality_propagation;
bool freeze_all; // freezing variables (for incremental solving)
virtual optionalt<literalt> literal(const exprt &expr) const;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why this line was moved, but certainly the comment must move together with it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved it back

use_cache(true),
equality_propagation(true),
freeze_all(false),
post_processing_done(false),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

" Initialize at declaration instead of construction

This avoids having to initialize them the same way in all constructors."

There just seems to be a single constructor - what sort of story is being made up here?!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should another constructor be added in the future (copy, move, default, overload of existing constructor), we'll have one set of default arguments, and the user won't have to repeat himself. Also, they're much easier to look up now - they're just when the values are declared. No need to dig through the .cpp file looking for the constructor that isn't there.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get your point, but I don't want to be lied to in the commit message.

  1. They will always be initialised upon construction, the declaration is just a syntactic construct.
  2. There just is one constructor at this time, so don't use the plural.

If the commit message said what your comment said, all would be well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the commit message.

@@ -110,14 +110,14 @@ class prop_conv_solvert:public prop_convt

virtual void clear_cache() { cache.clear();}

typedef std::map<irep_idt, literalt> symbolst;
typedef std::unordered_map<exprt, literalt, irep_hash> cachet;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't do this. Type names also carry useful information about what a data structure is intended to hold/do.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typedefs do the opposite. Especially when concealing standard data structure, such as map. When reading code using typedefs this way one might think that cachet and symbolst is a user-defined class that needs special treatment. When explicitly written as std:::vector or std::map the interface and structure of the variable becomes obvious. The name of the variable should signify intent, not the type alias.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you please.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

symbolst is not great here, but cachet actually conveys intent.
Unless the typedef is only used once, I'm strongly in favour of using typedefs, in particular at interfaces.
If you think that typedefs hide information then you should use an IDE that solves that problem for you.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah; even if used once, these can still be worth having.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think symbolst is misleading (you would expect a vector of symbolt). cachet does carry the intent but it's already present in the variable name (cache).

if(auto s = numeric_cast<mp_integer>(size))
return unbounded_array == unbounded_arrayt::U_AUTO &&
*s > MAX_FLATTENED_ARRAY_SIZE;
return true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I don't see how this is a "simplification". I'd much more accept that if you really gathered all the bits together. How about this:

if(type.id()==ID_symbol)
  return is_unbounded_array(ns.follow(type));

const exprt &size=to_array_type(type).size();
return
  type.id() == ID_array &&
  (unbounded_array == unbounded_arrayt::U_ALL ||
   !(auto s = numeric_cast<mp_integer>(size)) ||
   (unbounded_array == unbounded_arrayt::U_AUTO &&
    *s > MAX_FLATTENED_ARRAY_SIZE));

Copy link
Contributor

@LAJW LAJW Feb 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return (auto s = func()) && s > 0) is not valid C++. if-declarations are allowed, but not this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thanks, so just declare s beforehand and just do the assignment in there.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah; not sure this change is actually a simplification. Also -- what problem is this solving?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed according to Michael's comment.

bv_sub.resize(bv.size());

for(std::size_t i=0; i<bv.size(); i++)
bv_sub[i].id(std::to_string(bv[i].get()));
return dest;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit pick: a blank line before returnwould help readability.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say removing double blank lines before this for loop would.

<< it.second.get_value(prop) << '\n';
}
for(const auto &pair : map.mapping)
out << pair.first << "=" << pair.second.get_value(prop) << '\n';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commit message doesn't really reflect what happened here (which mainly is renaming it -> pair (which is good).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the commit message: d400deb

if(width==0)
return conversion_failed(expr);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please stop removing blank lines that clearly separate steps. Code readability matters. Really.

#ifdef COMPACT_EQUAL_CONST
bv_utils.equal_const_register(convert_bv(index)); // Definitely
bv_utils.equal_const_register(convert_bv(result)); // Maybe
#endif
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this gone?


#ifdef COMPACT_EQUAL_CONST
bv_utils.equal_const_register(convert_bv(index)); // Definitely
#endif
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, where is this gone?


#ifdef COMPACT_EQUAL_CONST
bv_utils.equal_const_register(convert_bv(index)); // Definitely
#endif
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

@martin-cs
Copy link
Collaborator

I'll try to work through the commits as some of these are "yes, we should have done this years ago" and others seem problematic. As for the white-space removal -- I though the whole point of the long and bloody campaign to get clang-format working was so that we didn't have to argue about this. I'll check how many of my patch sets are broken by these changes as well.

const exprt &op0=expr.op0();

bool no_overflow=expr.id()=="no-overflow-mult";
const bool no_overflow = expr.id() == "no-overflow-mult";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to add an irepid for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it does not seem any exprt gets this id anywhere. So it may be better to just remove it. And same thing with the other no-overflows

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kroening Is there code elsewhere that generates no-overflow variants of the arithmetic expressions?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there isn't then there should be; it is useful to know whether overflow matters or not / can be ignored. Especially for multiplication.

{
prop.set_frozen(a);
}
void set_assumptions(const bvt &_assumptions) override
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the formatting of these one-line functions is inconsistent

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also unnecessary IMHO.

Copy link
Collaborator

@martin-cs martin-cs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About half of this patch set is brilliant and changes we should have made ages ago and we really should merge. Then there is a fair amount of reformatting and whitespace changes plus a few API changes which are wrong IMHO.

const exprt &expr,
const std::size_t bit,
literalt &dest) const
optionalt<literalt>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why this change is needed. If there is a real error then an exception should be thrown. If not then returning constant literals is acceptable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function is not returning a constant literal when there is an error, it is just leaving dest untouched. So this is not changing the behavior of the function, just making the interface clearer.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I suggest a different route: please just #ifdef 0 this code. Whether you change the interface to an optionalt I don't care as much then. (Background: I wanted to figure out whether throwing an exception would be fine. I couldn't tell, because the code is never used.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I get your comment. If I #ifdef 0 the function when cbmc will not build because get_bool is called from somewhere.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, this is not about get_bool, but about literal (both boolbvt::literal and prop_convt::literal).

@@ -128,7 +128,7 @@ class prop_conv_solvert:public prop_convt
bool post_processing_done;

// get a _boolean_ value from counterexample if not valid
virtual bool get_bool(const exprt &expr, tvt &value) const;
virtual optionalt<tvt> get_bool(const exprt &expr) const;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again; this doesn't seem broken and so doesn't need to be fixed. If someone gives a true expression in then it is perfectly OK to give a true literal back.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this doesn't change the behavior. The interface as it was before is confusing because you would think that the result of get_bool is the returned bool, while in fact it is stored in tvt &value (your comment makes me think you are confused yourself). Using optional makes it much clearer that it's actually a tvt which is the result.

(note to myself) the documentation should be updated.

@@ -305,29 +305,13 @@ literalt prop_conv_solvert::convert_rest(const exprt &expr)

bool prop_conv_solvert::set_equality_to_true(const equal_exprt &expr)
{
if(!equality_propagation)
if(!equality_propagation || expr.lhs().id() != ID_symbol)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this changed -- what problem does this solve?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's subjective but I find the second version much more readable.
The problems were missing const, uninformative result variable name, confusing fall through in the case of result.second being false.


bvt bv;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whitespace changes should be in their own commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reverted this change

@@ -110,14 +110,14 @@ class prop_conv_solvert:public prop_convt

virtual void clear_cache() { cache.clear();}

typedef std::map<irep_idt, literalt> symbolst;
typedef std::unordered_map<exprt, literalt, irep_hash> cachet;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah; even if used once, these can still be worth having.

if(op.size()!=width)
throw "convert_mult: unexpected operand width";

INVARIANT(op.size() == width, "convert_mult: unexpected operand width");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DATA_INVARIANT

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or CHECK_RETURN, but not just a plain INVARIANT

if(op0.type()!=expr.type())
throw "multiplication with mixed types";

INVARIANT(op0.type() == expr.type(), "multiplication with mixed types");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DATA_INVARIANT

if(bv.size()!=width)
throw "convert_mult: unexpected operand width";

INVARIANT(bv.size() == width, "convert_mult: unexpected operand width");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DATA_INVARIANT

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this one, it's more like we are checking that convert_bv did its work correctly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, CHECK_RETURN then.

if(it->type()!=expr.type())
throw "multiplication with mixed types";

INVARIANT(it->type() == expr.type(), "multiplication with mixed types");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DATA_INVARIANT

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually CHECK_RETURN.

{
prop.set_frozen(a);
}
void set_assumptions(const bvt &_assumptions) override
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also unnecessary IMHO.

Copy link
Collaborator

@tautschnig tautschnig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to my comment below, on the discussion of get_bool: it's probably not on a hot path so changing the interface isn't that a big deal. Actually it just has a single user in prop_conv_solvert::get.

Edit: shouldn't get_bool just 1. throw an exception if the expression type is not ID_bool and 2. return TV_UNKNOWN in all those cases where it presently returns true?

const exprt &expr,
const std::size_t bit,
literalt &dest) const
optionalt<literalt>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I suggest a different route: please just #ifdef 0 this code. Whether you change the interface to an optionalt I don't care as much then. (Background: I wanted to figure out whether throwing an exception would be fine. I couldn't tell, because the code is never used.)

@romainbrenguier romainbrenguier force-pushed the refactor/prop_conv branch 5 times, most recently from 1311f15 to bfeac6d Compare February 23, 2018 14:12
@martin-cs
Copy link
Collaborator

@romainbrenguier (author), @tautschnig (reviewer), @peterschrammel (other owner) how should we handle this? As it's now 43 commits it seems a little unwieldy and I'm worried that the majority of patches which are fine might get forgotten while we discuss the few controversial ones. Would it be entirely implausible to think of a meeting with three of us to work through and merge things?

@romainbrenguier
Copy link
Contributor Author

@martin-cs what I could first do is make a separate PR with the non controversial commits, could you list the ones that you think are ready to go (or the opposite)? I would be ok with a meeting but when can it happen?

@tautschnig
Copy link
Collaborator

@romainbrenguier Would you mind merging the fix-ups into their corresponding commits so that we have a clean list to mark as (non-)controversial?

@romainbrenguier
Copy link
Contributor Author

@tautschnig done

@tautschnig
Copy link
Collaborator

I'd list the following as straight forward:

  • Assert replaced by unreachable
  • Use ranged for
  • Renaming it to symbol
  • Initialize at declaration instead of construction
  • Remove virtual keyword where not needed (if restricted to what the commit subject says)
  • Replace throws by invariant or preconditions
  • Remove useless includes
  • Use auto for iterator types
  • Use standard algorithm for finding an element
  • Simplify dec_solve
  • Remove useless comments
  • Simplify loop in prop_conv::get
  • Remove useless virtual (though it should be squashed with "Remove virtual keyword" above)
  • Make make_(free_)bv_expr return exprt
  • Simplify boolbvt::set_to
  • Rename it to pair in boolbvt::print_assignment

The others vary in the amount of discussion required. Many of them include good changes mixed with unnecessary ones. All those that include changing throw ... -> INVARIANT should be discussed as there are design issues.

@@ -64,13 +55,13 @@ bvt boolbvt::convert_bitwise(const exprt &expr)
else if(expr.id()==ID_bitxnor)
bv[i]=prop.lequal(bv[i], op[i]);
else
throw "unexpected operand";
UNIMPLEMENTED;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe UNHANDLED_CASE?

}
}
}

return bv;
}

throw "unexpected bitwise operand";
UNIMPLEMENTED;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe UNHANDLED_CASE?

"__CPROVER_internal_actual_array_"+
std::to_string(actual_array_counter++);
const std::string identifier = "__CPROVER_internal_actual_array_" +
std::to_string(actual_array_counter++);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use CPROVER_PREFIX

@martin-cs
Copy link
Collaborator

Things I feel are straight forward:
Assert replaced by unreachable
Use ranged for
Remove useless includes
Use auto for iterator types
Simplify loop in prop_conv::get
Remove useless virtual
Make make_(free_)bv_expr return exprt
Replacing throws by invariants in boolbv convert
Make build_offset_map return an offset_mapt
Rename it to pair in boolbvt::print_assignment
Remove useless throws in boolbvt::convert_index
Refactoring in boolbvt::convert_abs
Refactoring in boolbvt::convert_bitwise
Refactoring in boolbvt::convert_byte_extract

Things that I am ambivalent about and if @tautschnig thinks they are clear cut, then let's have them in:
Refactoring set_equality_to_true
Renaming it to symbol
Initialize at declaration instead of construction
Remove virtual keyword where not needed
Replace throws by invariant or preconditions
Use standard algorithm for finding an element
Simplify dec_solve
Remove useless comments
Rename SUB in baset
Simplify is_unbounded_array
Simplify boolbvt::set_to
Refactoring in boolbvt::convert_add_sub
Refactoring in boolbvt::convert_array_of
Refactoring in boolbvt::convert_index
Refactoring in boolbvt::convert_mult
Reformat one line functions

Things I think need more discussion:
Make literal return an optional
Make get_bool return a optional
Do not use typedef for types used only once
Refactoring in boolbvt::convert_array

HTH

@romainbrenguier
Copy link
Contributor Author

@martin-cs @tautschnig thanks for making these lists of commits, here is a first PR in which I just selected the intersection of what Michael considers straightforward and what Martin considers straightforward or is ambivalent #1950
Sorry for the delay (I have other PRs to take care of and which were more urgent than this one)

@martin-cs
Copy link
Collaborator

martin-cs commented Mar 20, 2018 via email

@romainbrenguier
Copy link
Contributor Author

Another set of commits which Martin listed as straightforward #1956

@martin-cs
Copy link
Collaborator

@romainbrenguier My apologies this stalled; will pick up on #1956

This is clearer than a boolean signaling an error
The class actualy designated by this is a base class and not a
subclass, so baset is clearer.
Factor out is_uniform function to reduce size of big function and make
it clearer.
Add const keyword where possible.
Change assertions in INVARIANTS.
Remove blank lines that are not helping clarity (for example between
comments and instructions to which they apply).
Use numeric_cast instead of to_integer.
@romainbrenguier
Copy link
Contributor Author

Most commits have been merged via other PRs so I'm closing this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants