Skip to content

Commit c78c6c7

Browse files
committed
Use autoharness for ptr::align_offset::mod_inv
This also solves the problem that we cannot write a harness for a nested function, and hence previously had to copy it.
1 parent 472a428 commit c78c6c7

File tree

2 files changed

+9
-63
lines changed

2 files changed

+9
-63
lines changed

.github/workflows/kani.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ jobs:
7979
- name: Run Kani Verification
8080
run: |
8181
scripts/run-kani.sh --run autoharness --kani-args \
82+
--include-pattern ptr::align_offset::mod_inv \
8283
--include-pattern time::Duration::from_micros \
8384
--include-pattern time::Duration::from_millis \
8485
--include-pattern time::Duration::from_nanos \

library/core/src/ptr/mod.rs

Lines changed: 8 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1901,6 +1901,14 @@ pub(crate) unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
19011901
/// * `x < m`; (if `x ≥ m`, pass in `x % m` instead)
19021902
///
19031903
/// Implementation of this function shall not panic. Ever.
1904+
#[safety::requires(m.is_power_of_two())]
1905+
#[safety::requires(x < m)]
1906+
// TODO: add ensures contract to check that the answer is indeed correct
1907+
// This will require quantifiers (https://model-checking.github.io/kani/rfc/rfcs/0010-quantifiers.html)
1908+
// so that we can add a precondition that gcd(x, m) = 1 like so:
1909+
// ∀d, d > 0 ∧ x % d = 0 ∧ m % d = 0 → d = 1
1910+
// With this precondition, we can then write this postcondition to check the correctness of the answer:
1911+
// #[safety::ensures(|result| wrapping_mul(*result, x) % m == 1)]
19041912
#[inline]
19051913
const unsafe fn mod_inv(x: usize, m: usize) -> usize {
19061914
/// Multiplicative modular inverse table modulo 2⁴ = 16.
@@ -2509,67 +2517,4 @@ mod verify {
25092517
let p = kani::any::<usize>() as *const [char; 5];
25102518
check_align_offset(p);
25112519
}
2512-
2513-
// This function lives inside align_offset, so it is not publicly accessible (hence this copy).
2514-
#[safety::requires(m.is_power_of_two())]
2515-
#[safety::requires(x < m)]
2516-
// TODO: add ensures contract to check that the answer is indeed correct
2517-
// This will require quantifiers (https://model-checking.github.io/kani/rfc/rfcs/0010-quantifiers.html)
2518-
// so that we can add a precondition that gcd(x, m) = 1 like so:
2519-
// ∀d, d > 0 ∧ x % d = 0 ∧ m % d = 0 → d = 1
2520-
// With this precondition, we can then write this postcondition to check the correctness of the answer:
2521-
// #[safety::ensures(|result| wrapping_mul(*result, x) % m == 1)]
2522-
const unsafe fn mod_inv_copy(x: usize, m: usize) -> usize {
2523-
/// Multiplicative modular inverse table modulo 2⁴ = 16.
2524-
///
2525-
/// Note, that this table does not contain values where inverse does not exist (i.e., for
2526-
/// `0⁻¹ mod 16`, `2⁻¹ mod 16`, etc.)
2527-
const INV_TABLE_MOD_16: [u8; 8] = [1, 11, 13, 7, 9, 3, 5, 15];
2528-
/// Modulo for which the `INV_TABLE_MOD_16` is intended.
2529-
const INV_TABLE_MOD: usize = 16;
2530-
2531-
// SAFETY: `m` is required to be a power-of-two, hence non-zero.
2532-
let m_minus_one = unsafe { unchecked_sub(m, 1) };
2533-
let mut inverse = INV_TABLE_MOD_16[(x & (INV_TABLE_MOD - 1)) >> 1] as usize;
2534-
let mut mod_gate = INV_TABLE_MOD;
2535-
// We iterate "up" using the following formula:
2536-
//
2537-
// $$ xy ≡ 1 (mod 2ⁿ) → xy (2 - xy) ≡ 1 (mod 2²ⁿ) $$
2538-
//
2539-
// This application needs to be applied at least until `2²ⁿ ≥ m`, at which point we can
2540-
// finally reduce the computation to our desired `m` by taking `inverse mod m`.
2541-
//
2542-
// This computation is `O(log log m)`, which is to say, that on 64-bit machines this loop
2543-
// will always finish in at most 4 iterations.
2544-
loop {
2545-
// y = y * (2 - xy) mod n
2546-
//
2547-
// Note, that we use wrapping operations here intentionally – the original formula
2548-
// uses e.g., subtraction `mod n`. It is entirely fine to do them `mod
2549-
// usize::MAX` instead, because we take the result `mod n` at the end
2550-
// anyway.
2551-
if mod_gate >= m {
2552-
break;
2553-
}
2554-
inverse = wrapping_mul(inverse, wrapping_sub(2usize, wrapping_mul(x, inverse)));
2555-
let (new_gate, overflow) = mul_with_overflow(mod_gate, mod_gate);
2556-
if overflow {
2557-
break;
2558-
}
2559-
mod_gate = new_gate;
2560-
}
2561-
inverse & m_minus_one
2562-
}
2563-
2564-
// The specification for mod_inv states that it cannot ever panic.
2565-
// Verify that is the case, given that the function's safety preconditions are met.
2566-
2567-
// TODO: Once https://github.com/model-checking/kani/issues/3467 is fixed,
2568-
// move this harness inside `align_offset` and delete `mod_inv_copy`
2569-
#[kani::proof_for_contract(mod_inv_copy)]
2570-
fn check_mod_inv() {
2571-
let x = kani::any::<usize>();
2572-
let m = kani::any::<usize>();
2573-
unsafe { mod_inv_copy(x, m) };
2574-
}
25752520
}

0 commit comments

Comments
 (0)