Skip to content

Commit bc7bce7

Browse files
carolynzechtautschnigzhassan-aws
authored
align_offset Contracts (#69)
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses. --------- Co-authored-by: Michael Tautschnig <[email protected]> Co-authored-by: Zyad Hassan <[email protected]> Co-authored-by: Michael Tautschnig <[email protected]>
1 parent f16e297 commit bc7bce7

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed

library/core/src/ptr/mod.rs

+152
Original file line numberDiff line numberDiff line change
@@ -1884,6 +1884,39 @@ pub unsafe fn write_volatile<T>(dst: *mut T, src: T) {
18841884
///
18851885
/// Any questions go to @nagisa.
18861886
#[lang = "align_offset"]
1887+
#[safety::requires(a.is_power_of_two())]
1888+
#[safety::ensures(|result| {
1889+
let stride = mem::size_of::<T>();
1890+
// ZSTs
1891+
if stride == 0 {
1892+
if p.addr() % a == 0 {
1893+
return *result == 0;
1894+
} else {
1895+
return *result == usize::MAX;
1896+
}
1897+
}
1898+
1899+
// In this case, the pointer cannot be aligned
1900+
if (a % stride == 0) && (p.addr() % stride != 0) {
1901+
return *result == usize::MAX;
1902+
}
1903+
1904+
// Checking if the answer should indeed be usize::MAX when a % stride != 0
1905+
// requires computing gcd(a, stride), which is too expensive without
1906+
// quantifiers (https://model-checking.github.io/kani/rfc/rfcs/0010-quantifiers.html).
1907+
// This should be updated once quantifiers are available.
1908+
if (a % stride != 0 && *result == usize::MAX) {
1909+
return true;
1910+
}
1911+
1912+
// If we reach this case, either:
1913+
// - a % stride == 0 and p.addr() % stride == 0, so it is definitely possible to align the pointer
1914+
// - a % stride != 0 and result != usize::MAX, so align_offset is claiming that it's possible to align the pointer
1915+
// Check that applying the returned result does indeed produce an aligned address
1916+
let product = usize::wrapping_mul(*result, stride);
1917+
let new_addr = usize::wrapping_add(product, p.addr());
1918+
*result != usize::MAX && new_addr % a == 0
1919+
})]
18871920
pub(crate) const unsafe fn align_offset<T: Sized>(p: *const T, a: usize) -> usize {
18881921
// FIXME(#75598): Direct use of these intrinsics improves codegen significantly at opt-level <=
18891922
// 1, where the method versions of these operations are not inlined.
@@ -2380,6 +2413,9 @@ mod verify {
23802413
use crate::fmt::Debug;
23812414
use super::*;
23822415
use crate::kani;
2416+
use intrinsics::{
2417+
mul_with_overflow, unchecked_sub, wrapping_mul, wrapping_sub
2418+
};
23832419

23842420
#[kani::proof_for_contract(read_volatile)]
23852421
pub fn check_read_u128() {
@@ -2388,4 +2424,120 @@ mod verify {
23882424
let copy = unsafe { read_volatile(ptr) };
23892425
assert_eq!(val, copy);
23902426
}
2427+
2428+
fn check_align_offset<T>(p: *const T) {
2429+
let a = kani::any::<usize>();
2430+
unsafe { align_offset(p, a) };
2431+
}
2432+
2433+
#[kani::proof_for_contract(align_offset)]
2434+
fn check_align_offset_zst() {
2435+
let p = kani::any::<usize>() as *const ();
2436+
check_align_offset(p);
2437+
}
2438+
2439+
#[kani::proof_for_contract(align_offset)]
2440+
fn check_align_offset_u8() {
2441+
let p = kani::any::<usize>() as *const u8;
2442+
check_align_offset(p);
2443+
}
2444+
2445+
#[kani::proof_for_contract(align_offset)]
2446+
fn check_align_offset_u16() {
2447+
let p = kani::any::<usize>() as *const u16;
2448+
check_align_offset(p);
2449+
}
2450+
2451+
#[kani::proof_for_contract(align_offset)]
2452+
fn check_align_offset_u32() {
2453+
let p = kani::any::<usize>() as *const u32;
2454+
check_align_offset(p);
2455+
}
2456+
2457+
#[kani::proof_for_contract(align_offset)]
2458+
fn check_align_offset_u64() {
2459+
let p = kani::any::<usize>() as *const u64;
2460+
check_align_offset(p);
2461+
}
2462+
2463+
#[kani::proof_for_contract(align_offset)]
2464+
fn check_align_offset_u128() {
2465+
let p = kani::any::<usize>() as *const u128;
2466+
check_align_offset(p);
2467+
}
2468+
2469+
#[kani::proof_for_contract(align_offset)]
2470+
fn check_align_offset_4096() {
2471+
let p = kani::any::<usize>() as *const [u128; 64];
2472+
check_align_offset(p);
2473+
}
2474+
2475+
#[kani::proof_for_contract(align_offset)]
2476+
fn check_align_offset_17() {
2477+
let p = kani::any::<usize>() as *const [char; 17];
2478+
check_align_offset(p);
2479+
}
2480+
2481+
// This function lives inside align_offset, so it is not publicly accessible (hence this copy).
2482+
#[safety::requires(m.is_power_of_two())]
2483+
#[safety::requires(x < m)]
2484+
// TODO: add ensures contract to check that the answer is indeed correct
2485+
// This will require quantifiers (https://model-checking.github.io/kani/rfc/rfcs/0010-quantifiers.html)
2486+
// so that we can add a precondition that gcd(x, m) = 1 like so:
2487+
// ∀d, d > 0 ∧ x % d = 0 ∧ m % d = 0 → d = 1
2488+
// With this precondition, we can then write this postcondition to check the correctness of the answer:
2489+
// #[safety::ensures(|result| wrapping_mul(*result, x) % m == 1)]
2490+
const unsafe fn mod_inv_copy(x: usize, m: usize) -> usize {
2491+
/// Multiplicative modular inverse table modulo 2⁴ = 16.
2492+
///
2493+
/// Note, that this table does not contain values where inverse does not exist (i.e., for
2494+
/// `0⁻¹ mod 16`, `2⁻¹ mod 16`, etc.)
2495+
const INV_TABLE_MOD_16: [u8; 8] = [1, 11, 13, 7, 9, 3, 5, 15];
2496+
/// Modulo for which the `INV_TABLE_MOD_16` is intended.
2497+
const INV_TABLE_MOD: usize = 16;
2498+
2499+
// SAFETY: `m` is required to be a power-of-two, hence non-zero.
2500+
let m_minus_one = unsafe { unchecked_sub(m, 1) };
2501+
let mut inverse = INV_TABLE_MOD_16[(x & (INV_TABLE_MOD - 1)) >> 1] as usize;
2502+
let mut mod_gate = INV_TABLE_MOD;
2503+
// We iterate "up" using the following formula:
2504+
//
2505+
// $$ xy ≡ 1 (mod 2ⁿ) → xy (2 - xy) ≡ 1 (mod 2²ⁿ) $$
2506+
//
2507+
// This application needs to be applied at least until `2²ⁿ ≥ m`, at which point we can
2508+
// finally reduce the computation to our desired `m` by taking `inverse mod m`.
2509+
//
2510+
// This computation is `O(log log m)`, which is to say, that on 64-bit machines this loop
2511+
// will always finish in at most 4 iterations.
2512+
loop {
2513+
// y = y * (2 - xy) mod n
2514+
//
2515+
// Note, that we use wrapping operations here intentionally – the original formula
2516+
// uses e.g., subtraction `mod n`. It is entirely fine to do them `mod
2517+
// usize::MAX` instead, because we take the result `mod n` at the end
2518+
// anyway.
2519+
if mod_gate >= m {
2520+
break;
2521+
}
2522+
inverse = wrapping_mul(inverse, wrapping_sub(2usize, wrapping_mul(x, inverse)));
2523+
let (new_gate, overflow) = mul_with_overflow(mod_gate, mod_gate);
2524+
if overflow {
2525+
break;
2526+
}
2527+
mod_gate = new_gate;
2528+
}
2529+
inverse & m_minus_one
2530+
}
2531+
2532+
// The specification for mod_inv states that it cannot ever panic.
2533+
// Verify that is the case, given that the function's safety preconditions are met.
2534+
2535+
// TODO: Once https://github.com/model-checking/kani/issues/3467 is fixed,
2536+
// move this harness inside `align_offset` and delete `mod_inv_copy`
2537+
#[kani::proof_for_contract(mod_inv_copy)]
2538+
fn check_mod_inv() {
2539+
let x = kani::any::<usize>();
2540+
let m = kani::any::<usize>();
2541+
unsafe { mod_inv_copy(x, m) };
2542+
}
23912543
}

0 commit comments

Comments
 (0)