Skip to content

Commit 73b4c47

Browse files
qinhepingadpaco-awszhassan-aws
authored
Fix codegen_atomic_binop for atomic_ptr (#3047)
Fetch functions of `atomic_ptr` calls atomic intrinsic functions with pointer-type arguments (`invalid_mut`), which will cause typecheck failures. The change in this commit add support of pointer-type arguments into `codegen_atomic_binop` to fix the issue. The new `codegen_atomic_binop` will cast pointer arguments to `size_t`, apply op on them, and then cast the op result back to the pointer type. The new test include all fetch functions of `atomic_ptr` except for `fetch_ptr_add` and `fetch_ptr_sub`, which do not call intrinsic functions. Resolves #3042. --------- Co-authored-by: Adrian Palacios <73246657+adpaco-aws@users.noreply.github.com> Co-authored-by: Zyad Hassan <88045115+zhassan-aws@users.noreply.github.com>
1 parent 9b9094d commit 73b4c47

File tree

2 files changed

+93
-1
lines changed
  • kani-compiler/src/codegen_cprover_gotoc/codegen
  • tests/kani/Intrinsics/Atomic/Stable/AtomicPtr

2 files changed

+93
-1
lines changed

kani-compiler/src/codegen_cprover_gotoc/codegen/intrinsic.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,19 @@ impl<'tcx> GotocCtx<'tcx> {
239239
// *var1 = op(*var1, var2);
240240
// var = tmp;
241241
// -------------------------
242+
//
243+
// In fetch functions of atomic_ptr such as https://doc.rust-lang.org/std/sync/atomic/struct.AtomicPtr.html#method.fetch_byte_add,
244+
// the type of var2 can be pointer (invalid_mut).
245+
// In such case, atomic binops are transformed as follows to avoid typecheck failure.
246+
// -------------------------
247+
// var = atomic_op(var1, var2)
248+
// -------------------------
249+
// unsigned char tmp;
250+
// tmp = *var1;
251+
// *var1 = (typeof var1)op((size_t)*var1, (size_t)var2);
252+
// var = tmp;
253+
// -------------------------
254+
//
242255
// Note: Atomic arithmetic operations wrap around on overflow.
243256
macro_rules! codegen_atomic_binop {
244257
($op: ident) => {{
@@ -249,7 +262,14 @@ impl<'tcx> GotocCtx<'tcx> {
249262
let (tmp, decl_stmt) =
250263
self.decl_temp_variable(var1.typ().clone(), Some(var1.to_owned()), loc);
251264
let var2 = fargs.remove(0);
252-
let op_expr = (var1.clone()).$op(var2).with_location(loc);
265+
let op_expr = if var2.typ().is_pointer() {
266+
(var1.clone().cast_to(Type::c_size_t()))
267+
.$op(var2.cast_to(Type::c_size_t()))
268+
.with_location(loc)
269+
.cast_to(var1.typ().clone())
270+
} else {
271+
(var1.clone()).$op(var2).with_location(loc)
272+
};
253273
let assign_stmt = (var1.clone()).assign(op_expr, loc);
254274
let res_stmt = self.codegen_expr_to_place_stable(place, tmp.clone());
255275
Stmt::atomic_block(vec![decl_stmt, assign_stmt, res_stmt], loc)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
// Test atomic intrinsics through the stable interface of atomic_ptr.
5+
// Specifically, it checks that Kani correctly handles atomic_ptr's fetch methods, in which the second argument is a pointer type.
6+
// These methods were not correctly handled as explained in https://github.com/model-checking/kani/issues/3042.
7+
8+
#![feature(strict_provenance_atomic_ptr, strict_provenance)]
9+
use std::sync::atomic::{AtomicPtr, Ordering};
10+
11+
#[kani::proof]
12+
fn check_fetch_byte_add() {
13+
let atom = AtomicPtr::<i64>::new(core::ptr::null_mut());
14+
assert_eq!(atom.fetch_byte_add(1, Ordering::Relaxed).addr(), 0);
15+
// Note: in units of bytes, not `size_of::<i64>()`.
16+
assert_eq!(atom.load(Ordering::Relaxed).addr(), 1);
17+
}
18+
19+
#[kani::proof]
20+
fn check_fetch_byte_sub() {
21+
let atom = AtomicPtr::<i64>::new(core::ptr::invalid_mut(1));
22+
assert_eq!(atom.fetch_byte_sub(1, Ordering::Relaxed).addr(), 1);
23+
assert_eq!(atom.load(Ordering::Relaxed).addr(), 0);
24+
}
25+
26+
#[kani::proof]
27+
fn check_fetch_and() {
28+
let pointer = &mut 3i64 as *mut i64;
29+
// A tagged pointer
30+
let atom = AtomicPtr::<i64>::new(pointer.map_addr(|a| a | 1));
31+
assert_eq!(atom.fetch_or(1, Ordering::Relaxed).addr() & 1, 1);
32+
// Untag, and extract the previously tagged pointer.
33+
let untagged = atom.fetch_and(!1, Ordering::Relaxed).map_addr(|a| a & !1);
34+
assert_eq!(untagged, pointer);
35+
}
36+
37+
#[kani::proof]
38+
fn check_fetch_or() {
39+
let pointer = &mut 3i64 as *mut i64;
40+
41+
let atom = AtomicPtr::<i64>::new(pointer);
42+
// Tag the bottom bit of the pointer.
43+
assert_eq!(atom.fetch_or(1, Ordering::Relaxed).addr() & 1, 0);
44+
// Extract and untag.
45+
let tagged = atom.load(Ordering::Relaxed);
46+
assert_eq!(tagged.addr() & 1, 1);
47+
assert_eq!(tagged.map_addr(|p| p & !1), pointer);
48+
}
49+
50+
#[kani::proof]
51+
fn check_fetch_update() {
52+
let ptr: *mut _ = &mut 5;
53+
let some_ptr = AtomicPtr::new(ptr);
54+
55+
let new: *mut _ = &mut 10;
56+
assert_eq!(some_ptr.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |_| None), Err(ptr));
57+
let result = some_ptr.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| {
58+
if x == ptr { Some(new) } else { None }
59+
});
60+
assert_eq!(result, Ok(ptr));
61+
assert_eq!(some_ptr.load(Ordering::SeqCst), new);
62+
}
63+
64+
#[kani::proof]
65+
fn check_fetch_xor() {
66+
let pointer = &mut 3i64 as *mut i64;
67+
let atom = AtomicPtr::<i64>::new(pointer);
68+
69+
// Toggle a tag bit on the pointer.
70+
atom.fetch_xor(1, Ordering::Relaxed);
71+
assert_eq!(atom.load(Ordering::Relaxed).addr() & 1, 1);
72+
}

0 commit comments

Comments
 (0)