Skip to content

Commit 60cb531

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 5adba8b commit 60cb531

File tree

2 files changed

+9
-63
lines changed

2 files changed

+9
-63
lines changed

.github/workflows/kani.yml

+1
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

+8-63
Original file line numberDiff line numberDiff line change
@@ -1941,6 +1941,14 @@ pub(crate) unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
19411941
/// * `x < m`; (if `x ≥ m`, pass in `x % m` instead)
19421942
///
19431943
/// Implementation of this function shall not panic. Ever.
1944+
#[safety::requires(m.is_power_of_two())]
1945+
#[safety::requires(x < m)]
1946+
// TODO: add ensures contract to check that the answer is indeed correct
1947+
// This will require quantifiers (https://model-checking.github.io/kani/rfc/rfcs/0010-quantifiers.html)
1948+
// so that we can add a precondition that gcd(x, m) = 1 like so:
1949+
// ∀d, d > 0 ∧ x % d = 0 ∧ m % d = 0 → d = 1
1950+
// With this precondition, we can then write this postcondition to check the correctness of the answer:
1951+
// #[safety::ensures(|result| wrapping_mul(*result, x) % m == 1)]
19441952
#[inline]
19451953
const unsafe fn mod_inv(x: usize, m: usize) -> usize {
19461954
/// Multiplicative modular inverse table modulo 2⁴ = 16.
@@ -2549,67 +2557,4 @@ mod verify {
25492557
let p = kani::any::<usize>() as *const [char; 5];
25502558
check_align_offset(p);
25512559
}
2552-
2553-
// This function lives inside align_offset, so it is not publicly accessible (hence this copy).
2554-
#[safety::requires(m.is_power_of_two())]
2555-
#[safety::requires(x < m)]
2556-
// TODO: add ensures contract to check that the answer is indeed correct
2557-
// This will require quantifiers (https://model-checking.github.io/kani/rfc/rfcs/0010-quantifiers.html)
2558-
// so that we can add a precondition that gcd(x, m) = 1 like so:
2559-
// ∀d, d > 0 ∧ x % d = 0 ∧ m % d = 0 → d = 1
2560-
// With this precondition, we can then write this postcondition to check the correctness of the answer:
2561-
// #[safety::ensures(|result| wrapping_mul(*result, x) % m == 1)]
2562-
const unsafe fn mod_inv_copy(x: usize, m: usize) -> usize {
2563-
/// Multiplicative modular inverse table modulo 2⁴ = 16.
2564-
///
2565-
/// Note, that this table does not contain values where inverse does not exist (i.e., for
2566-
/// `0⁻¹ mod 16`, `2⁻¹ mod 16`, etc.)
2567-
const INV_TABLE_MOD_16: [u8; 8] = [1, 11, 13, 7, 9, 3, 5, 15];
2568-
/// Modulo for which the `INV_TABLE_MOD_16` is intended.
2569-
const INV_TABLE_MOD: usize = 16;
2570-
2571-
// SAFETY: `m` is required to be a power-of-two, hence non-zero.
2572-
let m_minus_one = unsafe { unchecked_sub(m, 1) };
2573-
let mut inverse = INV_TABLE_MOD_16[(x & (INV_TABLE_MOD - 1)) >> 1] as usize;
2574-
let mut mod_gate = INV_TABLE_MOD;
2575-
// We iterate "up" using the following formula:
2576-
//
2577-
// $$ xy ≡ 1 (mod 2ⁿ) → xy (2 - xy) ≡ 1 (mod 2²ⁿ) $$
2578-
//
2579-
// This application needs to be applied at least until `2²ⁿ ≥ m`, at which point we can
2580-
// finally reduce the computation to our desired `m` by taking `inverse mod m`.
2581-
//
2582-
// This computation is `O(log log m)`, which is to say, that on 64-bit machines this loop
2583-
// will always finish in at most 4 iterations.
2584-
loop {
2585-
// y = y * (2 - xy) mod n
2586-
//
2587-
// Note, that we use wrapping operations here intentionally – the original formula
2588-
// uses e.g., subtraction `mod n`. It is entirely fine to do them `mod
2589-
// usize::MAX` instead, because we take the result `mod n` at the end
2590-
// anyway.
2591-
if mod_gate >= m {
2592-
break;
2593-
}
2594-
inverse = wrapping_mul(inverse, wrapping_sub(2usize, wrapping_mul(x, inverse)));
2595-
let (new_gate, overflow) = mul_with_overflow(mod_gate, mod_gate);
2596-
if overflow {
2597-
break;
2598-
}
2599-
mod_gate = new_gate;
2600-
}
2601-
inverse & m_minus_one
2602-
}
2603-
2604-
// The specification for mod_inv states that it cannot ever panic.
2605-
// Verify that is the case, given that the function's safety preconditions are met.
2606-
2607-
// TODO: Once https://github.com/model-checking/kani/issues/3467 is fixed,
2608-
// move this harness inside `align_offset` and delete `mod_inv_copy`
2609-
#[kani::proof_for_contract(mod_inv_copy)]
2610-
fn check_mod_inv() {
2611-
let x = kani::any::<usize>();
2612-
let m = kani::any::<usize>();
2613-
unsafe { mod_inv_copy(x, m) };
2614-
}
26152560
}

0 commit comments

Comments
 (0)