Skip to content

Conversation

@zhassan-aws
Copy link
Contributor

@zhassan-aws zhassan-aws commented Jan 6, 2023

Description of changes:

Adds two methods that allow the user to select the solver to use with CBMC:

  1. A new command-line --solver option
  2. A kani::solver attribute

The kani::solver attribute allows selecting a different solver for each harness, e.g.

#[kani::proof]
#[kani::solver(kissat)]
fn check() { ... }

If the command-line --solver option is specified, it overrides the solver specified in the attribute (if any).

Valid values are kissat and minisat. Passing a custom SAT solver binary can be done with:

#[kani::solver(custom = "my_sat_solver")]

This case gets passed to CBMC's --external-sat-solver option directly.

Resolved issues:

Resolves #1656

Related RFC:

Optional #ISSUE-NUMBER.

Call-outs:

Testing:

  • How is this change tested? Added several tests

  • Is this a refactor change? No

Checklist

  • Each commit message has a non-empty body, explaining why the change was made
  • Methods or procedures are documented
  • Regression or unit tests are included, or existing tests cover the modified code
  • My PR is restricted to a single feature or bugfix

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.

@zhassan-aws zhassan-aws force-pushed the solver-attr branch 2 times, most recently from 84adc14 to 97b40f8 Compare January 6, 2023 18:37
@celinval
Copy link
Contributor

celinval commented Jan 6, 2023

I would prefer if the attribute is based on an ID, not a path. I don't think it's a good practice to hardcode paths in the code, since it is hard to ensure that the path works for every developer and the CI.

To make this extensible for users that want to try something new that doesn't ship with Kani, I would prefer adding installation instructions, command line or a utility that configures a new engine via its path into a format / path that Kani expects.

@zhassan-aws
Copy link
Contributor Author

I would prefer if the attribute is based on an ID, not a path. I don't think it's a good practice to hardcode paths in the code, since it is hard to ensure that the path works for every developer and the CI.

Point taken. A kani::solver attribute should be ID-based to support future plans of integrating multiple backends.

For the purposes of allowing the user to control CBMC's SAT solver specifically (which this PR is targeting), I can think of two options:

  1. Introduce a CBMC-specific annotation, e.g. kani::cbmc_solver or even kani::cbmc_sat_solver where the argument would be kissat, cadical, etc.
  2. Use kani::solver, and prefix each SAT solver option with cbmc (as we discussed offline), e.g. cbmc_kissat, cbmc_cadical, etc.

The advantage of option 1 is that it would keep the more valuable/more generic kani::solver attribute free, allowing us some time to design it based on future plans for integrating more backends.

The advantage of option 2 is that the user only needs to learn about one attribute for specifying the solver (i.e. fewer control knobs) which makes the usage simpler.

Thoughts?

@rahulku
Copy link
Contributor

rahulku commented Jan 7, 2023

Option 2 sounds better.

@celinval
Copy link
Contributor

celinval commented Jan 7, 2023

Just a nit... maybe use "::" as a convention, it is also less likely that "::" will be in the same of a binary.

I was thinking, since we only have cbmc today, we could consider that things that an id that doesn't have "::" uses cbmc. I.e.: kani::solver("minisat") is the same as kani::solver("cbmc::minisat").

Another option would be to start with what you have, and once we add more options, we use key / value for the attribute. And we support the previous mode as legacy purpose. I.e.:
kani::solver("minisat") == kani::solver(engine="cbmc", sat_solver="minisat")

@adpaco-aws
Copy link
Contributor

Thanks for pushing on this, @zhassan-aws !

Why are we adding a new attribute? We could just make engine and solver optional arguments in kani::proof, your example would look like this:

#[kani::proof(solver = kissat)]
fn check() { ... }

This idea comes from @fzaiser in #1656, and in my opinion is much cleaner than the other options considered here. It'd work in a similar way to @celinval 's last suggestion, assuming engine = cbmc and solver = minisat at the moment.

I don't think engines and solvers should be coupled in any of these options neither (i.e., things like cbmc_kissat or cbmc::kissat). Is there a technical reason for us to keep them that way? Even if newer backends only work with certain solvers, we can always limit these choices from Kani, but in principle we should aim for maximum flexibility.

@zhassan-aws
Copy link
Contributor Author

Why are we adding a new attribute? We could just make engine and solver optional arguments in kani::proof:

I fully agree with making attributes arguments to kani::proof, and have proposed it in #1217.

@zhassan-aws zhassan-aws changed the title Add a kani::solver attribute to allow specifying a SAT solver Allow specifying the CBMC SAT solver Jan 27, 2023
@zhassan-aws zhassan-aws changed the title Allow specifying the CBMC SAT solver Allow user to specify CBMC's solver Jan 27, 2023
@zhassan-aws zhassan-aws marked this pull request as ready for review January 27, 2023 19:59
@zhassan-aws zhassan-aws requested a review from a team as a code owner January 27, 2023 19:59
@jaisnan
Copy link
Contributor

jaisnan commented Jan 31, 2023

Are we going in the direction of adding a new attribute vs attribute arguments?

@zhassan-aws
Copy link
Contributor Author

Are we going in the direction of adding a new attribute vs attribute arguments?

We agreed that we will support both approaches.

@celinval
Copy link
Contributor

I have a few comments regarding the UX from the PR description:

  1. We should also accept minisat as a value.
  2. We should create a --list-sovers that prints all the solvers known to Kani.
  3. I was wondering if we should consider adding a --default-solver similar to the --default-unwind option.

@zhassan-aws
Copy link
Contributor Author

  • We should also accept minisat as a value.

Make sense.

  • We should create a --list-sovers that prints all the solvers known to Kani.

Good idea.

  • I was wondering if we should consider adding a --default-solver similar to the --default-unwind option.

TBH, I don't like --default-unwind, and I haven't seen it being used much. The behavior that makes sense to me is that command-line options should override attributes.

@celinval
Copy link
Contributor

  • We should also accept minisat as a value.

Make sense.

  • We should create a --list-sovers that prints all the solvers known to Kani.

Good idea.

  • I was wondering if we should consider adding a --default-solver similar to the --default-unwind option.

TBH, I don't like --default-unwind, and I haven't seen it being used much. The behavior that makes sense to me is that command-line options should override attributes.

Sounds good to me.

@zhassan-aws
Copy link
Contributor Author

Added minisat in e426f79.

For --list-solvers, I just recalled that the help already displays the options:

$ kani --help
...
      --solver <SOLVER>
          Specify the CBMC solver to use. Overrides the harness `solver` attribute
          [possible values: "SAT solver binary", kissat, minisat]
...

Is this sufficient?

@zhassan-aws
Copy link
Contributor Author

I don't think the arguments to the annotation should be strings.

I updated the PR to not use strings, e.g. #[kani::solver(kissat)]. Custom solver binaries can be specified using #[kani::solver(custom = "my_solver_binary")].

@zhassan-aws
Copy link
Contributor Author

I do wonder if we should make a distinction between a builtin solver and a custom one. So:

Done. This only works for the attribute though, but not the command-line option :( It seems that to support passing --solver custom="solver_binary", we would need to parse the argument ourselves. In this case, we would lose the automatic listing of variants in the help message that strum provides.

@adpaco-aws
Copy link
Contributor

I updated the PR to not use strings, e.g. #[kani::solver(kissat)]. Custom solver binaries can be specified using #[kani::solver(custom = "my_solver_binary")].

Thanks, @zhassan-aws !

One question: How would the user specify the solver in #[kani::proof(solver = ...)] if he wants to use a custom solver? I don't know if there's a syntax that'd support this case, since custom receives a string now, right?

@zhassan-aws
Copy link
Contributor Author

How would the user specify the solver in #[kani::proof(solver = ...)] if he wants to use a custom solver?

I'm not sure :) We could perhaps do kani::proof(solver = custom("solver-binary")).

Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

It looks good to me. Just some minor tweaks of the error messages.

CbmcSolver::Custom(custom_solver) => {
// Check if the specified binary exists in path
if which::which(custom_solver).is_err() {
eprintln!(
Copy link
Contributor

Choose a reason for hiding this comment

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

I was wondering if we should merge this eprintln!() and the bail() statements. If you do want to keep them separate, can you please use util::error instead to print this message so we keep our formatting consistent?

I think we are using stdout there but you can change it if you prefer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed the eprintln and moved its message to bail.

Some(CbmcSolver::Custom(lit.token_lit.symbol.to_string()))
}
_ => {
invalid_arg_err(attr);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you make this message more specific to the custom option?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This message is not specific to custom. It is emitted when the argument received is neither a MetaItemKind::Word or MetaItemKind::NameValue.

eprintln!(
"Error: The specified solver \"{custom_solver}\" was not found in path"
);
bail!("cbmc solver argument handling failed")
Copy link
Contributor

Choose a reason for hiding this comment

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

If you want to keep this separate from the error print, can you please make this more user friendly. Something like, "Custom solver resolution failed".

error: The `#[kani::solver]` attribute expects a single argument. Got 0 arguments.
test.rs:5:1
|
5 | #[kani::solver]
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: remove the line number from the expected files and add "" to the end of each line.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

@adpaco-aws adpaco-aws left a comment

Choose a reason for hiding this comment

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

Just a few comments about error messages.

I'd like us to iterate on the best option to pass the path for custom solvers. In my opinion, there shouldn't be any differences between the CLI and the annotation, which AFAIK is not true at the moment. But I don't want this to block the contribution since it's a special, non-required case for me.

Deserialize
)]
#[strum(serialize_all = "snake_case")]
pub enum CbmcSolver {
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the reason for this to be CbmcSolver and not just Solver?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the solver is currently specific for CBMC, and we're not yet clear on future plans, and whether this solver will apply to other engines as well, I added CBMC to its name.

Copy link
Contributor

Choose a reason for hiding this comment

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

I tend to agree with @adpaco-aws here, but this is an implementation detail that is internal to our code, so we can easily rename this later.

@zhassan-aws
Copy link
Contributor Author

In my opinion, there shouldn't be any differences between the CLI and the annotation, which AFAIK is not true at the moment.

I implemented a clap parser for the --solver option, so that we allow --solver kissat, --solver minisat, or --solver custom=<any-binary>. I added a couple of tests for it.

@celinval this is something that you have requested as well.

Copy link
Contributor

@adpaco-aws adpaco-aws left a comment

Choose a reason for hiding this comment

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

Thanks, @zhassan-aws ! Looks great overall, just a few suggestions 😄

@danielsn danielsn merged commit 4dfff36 into model-checking:main Feb 8, 2023
Copy link
Contributor

@celinval celinval left a comment

Choose a reason for hiding this comment

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

It looks good. Just please remove the remaining line numbers from the expected tests.

Comment on lines +3 to +5
|\
5 | #[kani::solver(123)]\
| ^^^^^^^^^^^^^^^^^^^^
Copy link
Contributor

Choose a reason for hiding this comment

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

Removing the line number:

Suggested change
|\
5 | #[kani::solver(123)]\
| ^^^^^^^^^^^^^^^^^^^^
|\
| #[kani::solver(123)]\
| ^^^^^^^^^^^^^^^^^^^^

Comment on lines +3 to +5
|\
5 | #[kani::solver(kissat, minisat)]\
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
|\
5 | #[kani::solver(kissat, minisat)]\
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|\
| #[kani::solver(kissat, minisat)]\
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Comment on lines +3 to +5
|\
5 | #[kani::solver(kissat)]\
| ^^^^^^^^^^^^^^^^^^^^^^^
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
|\
5 | #[kani::solver(kissat)]\
| ^^^^^^^^^^^^^^^^^^^^^^^
|\
| #[kani::solver(kissat)]\
| ^^^^^^^^^^^^^^^^^^^^^^^

Comment on lines +3 to +5
|\
5 | #[kani::solver]\
| ^^^^^^^^^^^^^^^
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
|\
5 | #[kani::solver]\
| ^^^^^^^^^^^^^^^
|\
| #[kani::solver]\
| ^^^^^^^^^^^^^^^

Comment on lines +3 to +5
|\
5 | #[kani::solver(foo)]\
| ^^^^^^^^^^^^^^^^^^^^
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
|\
5 | #[kani::solver(foo)]\
| ^^^^^^^^^^^^^^^^^^^^
|\
| #[kani::solver(foo)]\
| ^^^^^^^^^^^^^^^^^^^^

zhassan-aws added a commit to zhassan-aws/kani that referenced this pull request Feb 8, 2023
zhassan-aws added a commit that referenced this pull request Feb 8, 2023
@zhassan-aws zhassan-aws deleted the solver-attr branch February 8, 2023 19:32
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.

Feature request: make it easier to use other SAT solvers (e.g. kissat)

6 participants