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

Use CBMC's arithmetic operators that return both the result and whether it overflowed #1426

Merged

Conversation

zhassan-aws
Copy link
Contributor

Description of changes:

Kani's current implementation of the arithmetic with overflow operators, e.g. std::intrinsics::mul_with_overflow, involves performing the arithmetic operation twice: once to get the result, and another to determine whether it overflowed. CBMC recently introduced a new operation in diffblue/cbmc#6903 that performs a single arithmetic operation that returns a struct with two fields: the result and whether it overflowed. This PR switches the implementation of those intrinsics to the more efficient CBMC operation.

Resolved issues:

Resolves #ISSUE-NUMBER

Call-outs:

I kept the old implementations around (e.g. cprover_bindings::goto_program::expr::Expr::mul_overflow) because I haven't replaced all the code that uses them. The remaining usages, e.g. by kani_compiler::codegen_cprover_gotoc::codegen::intrinsic::GotocCtx::count_in_bytes require a bit more refactoring that I'll leave to a future PR.

Testing:

  • How is this change tested? Evaluated performance on a few tests:
#[kani::proof]
fn check_repeat() {
    let x: u64 = kani::any();
    let y: u64 = kani::any();
    if let Some(result) = x.checked_mul(y) {
        assert!(x == 0 || y == 0 || result != 0);
    }
}

Before: 0.765
After: 0.0788
Speedup: 9.7

#[kani::proof]
fn check_repeat() {
    let x: u64 = kani::any();
    let y: u64 = kani::any();
    kani::assume(x == 0 || y == 0);
    let result = x.checked_mul(y).unwrap();
    assert_eq!(result, 0);
}

Before: 0.090
After: 0.0689
Speedup: 1.3

#[kani::proof]
fn check_repeat() {
    let x: u64 = u64::MAX / 2;
    let y: u64 = kani::any();
    kani::assume(y > 2);
    let result = x.saturating_mul(y);
    assert_eq!(result, u64::MAX);
}

Before: 0.074
After: 0.060
Speedup: 1.2

  • 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 requested a review from a team as a code owner July 29, 2022 18:36
cprover_bindings/src/goto_program/expr.rs Outdated Show resolved Hide resolved
@@ -1296,6 +1325,12 @@ impl Expr {
ArithmeticOverflowResult { result, overflowed }
}

/// Uses CBMC's add-with-overflow operation that performs a single addition
/// operation
Copy link
Contributor

Choose a reason for hiding this comment

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

Give C or pseudocode example

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.

// add to symbol table
let res_type = arithmetic_overflow_result_type(op_type.clone());
let struct_tag =
self.ensure_struct(res_type.tag().unwrap(), res_type.tag().unwrap(), |_, _| {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do this here? I'd expect it to happen in arithmetic_overflow_result_type

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 was hoping to do it in arithmetic_overflow_result_type, but it doesn't have access to the Gotoc context since it's in the cprover_bindings crate.

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe make a helper function

let res_type = self.codegen_arithmetic_overflow_result_type(op_type.clone()); // does the ensure
let struct_tag = res_type.tag().unwrap()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. Done.

Comment on lines 233 to +237
let check = self.codegen_assert(
res.overflowed.not(),
var.clone()
.member(ARITH_OVERFLOW_OVERFLOWED_FIELD, &self.symbol_table)
.cast_to(Type::c_bool())
.not(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Were you going to split this off?

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 case wasn't difficult to handle. The other cases, e.g. in count_in_bytes, codegen_ptr_offset_from_expr, and codegen_ptr_offset_from_unsigned are a bit more challenging, and I'm splitting those off to a later PR.

Copy link
Contributor

@tedinski tedinski left a comment

Choose a reason for hiding this comment

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

I really hate these macros, but lgtm

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.

3 participants