Skip to content

Commit ba251bf

Browse files
reisneraadpaco-aws
andauthored
Add integer overflow checking for simd_div and simd_rem (rust-lang#2645)
Co-authored-by: Adrian Palacios <[email protected]>
1 parent 2391295 commit ba251bf

File tree

6 files changed

+173
-11
lines changed

6 files changed

+173
-11
lines changed

docs/src/rust-feature-support/intrinsics.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ Name | Support | Notes |
258258
--- | --- | --- |
259259
`simd_add` | Yes | |
260260
`simd_and` | Yes | |
261-
`simd_div` | Yes | Doesn't check for overflow cases [#1970](https://github.com/model-checking/kani/issues/1970) |
261+
`simd_div` | Yes | |
262262
`simd_eq` | Yes | |
263263
`simd_extract` | Yes | |
264264
`simd_ge` | Yes | |
@@ -269,9 +269,9 @@ Name | Support | Notes |
269269
`simd_mul` | Yes | |
270270
`simd_ne` | Yes | |
271271
`simd_or` | Yes | |
272-
`simd_rem` | Yes | Doesn't check for overflow cases [#1970](https://github.com/model-checking/kani/issues/1970) |
273-
`simd_shl` | Yes | Doesn't check for overflow cases [#1963](https://github.com/model-checking/kani/issues/1963) |
274-
`simd_shr` | Yes | Doesn't check for overflow cases [#1963](https://github.com/model-checking/kani/issues/1963) |
272+
`simd_rem` | Yes | Doesn't check for floating point overflow [#2669](https://github.com/model-checking/kani/issues/2669) |
273+
`simd_shl` | Yes | |
274+
`simd_shr` | Yes | |
275275
`simd_shuffle*` | Yes | |
276276
`simd_sub` | Yes | |
277277
`simd_xor` | Yes | |

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

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -511,9 +511,11 @@ impl<'tcx> GotocCtx<'tcx> {
511511
loc,
512512
),
513513
"simd_and" => codegen_intrinsic_binop!(bitand),
514-
// TODO: `simd_div` and `simd_rem` don't check for overflow cases.
515-
// <https://github.com/model-checking/kani/issues/1970>
516-
"simd_div" => codegen_intrinsic_binop!(div),
514+
// TODO: `simd_rem` doesn't check for overflow cases for floating point operands.
515+
// <https://github.com/model-checking/kani/pull/2645>
516+
"simd_div" | "simd_rem" => {
517+
self.codegen_simd_div_with_overflow(fargs, intrinsic, p, loc)
518+
}
517519
"simd_eq" => self.codegen_simd_cmp(Expr::vector_eq, fargs, p, span, farg_types, ret_ty),
518520
"simd_extract" => {
519521
self.codegen_intrinsic_simd_extract(fargs, p, farg_types, ret_ty, span)
@@ -537,9 +539,6 @@ impl<'tcx> GotocCtx<'tcx> {
537539
self.codegen_simd_cmp(Expr::vector_neq, fargs, p, span, farg_types, ret_ty)
538540
}
539541
"simd_or" => codegen_intrinsic_binop!(bitor),
540-
// TODO: `simd_div` and `simd_rem` don't check for overflow cases.
541-
// <https://github.com/model-checking/kani/issues/1970>
542-
"simd_rem" => codegen_intrinsic_binop!(rem),
543542
"simd_shl" | "simd_shr" => {
544543
self.codegen_simd_shift_with_distance_check(fargs, intrinsic, p, loc)
545544
}
@@ -1552,6 +1551,39 @@ impl<'tcx> GotocCtx<'tcx> {
15521551
self.codegen_expr_to_place(p, e)
15531552
}
15541553

1554+
/// Codegen for `simd_div` and `simd_rem` intrinsics.
1555+
/// This checks for overflow in signed integer division (i.e. when dividing the minimum integer
1556+
/// for the type by -1). Overflow checks on floating point division are handled by CBMC, as is
1557+
/// division by zero for both integers and floats.
1558+
fn codegen_simd_div_with_overflow(
1559+
&mut self,
1560+
fargs: Vec<Expr>,
1561+
intrinsic: &str,
1562+
p: &Place<'tcx>,
1563+
loc: Location,
1564+
) -> Stmt {
1565+
let op_fun = match intrinsic {
1566+
"simd_div" => Expr::div,
1567+
"simd_rem" => Expr::rem,
1568+
_ => unreachable!("expected simd_div or simd_rem"),
1569+
};
1570+
let base_type = fargs[0].typ().base_type().unwrap().clone();
1571+
if base_type.is_integer() && base_type.is_signed(self.symbol_table.machine_model()) {
1572+
let min_int_expr = base_type.min_int_expr(self.symbol_table.machine_model());
1573+
let negative_one = Expr::int_constant(-1, base_type);
1574+
self.codegen_simd_op_with_overflow(
1575+
op_fun,
1576+
|a, b| a.eq(min_int_expr.clone()).and(b.eq(negative_one.clone())),
1577+
fargs,
1578+
intrinsic,
1579+
p,
1580+
loc,
1581+
)
1582+
} else {
1583+
self.binop(p, fargs, op_fun)
1584+
}
1585+
}
1586+
15551587
/// Intrinsics which encode a SIMD arithmetic operation with overflow check.
15561588
/// We expand the overflow check because CBMC overflow operations don't accept array as
15571589
/// argument.
@@ -1587,7 +1619,7 @@ impl<'tcx> GotocCtx<'tcx> {
15871619
);
15881620
let res = op_fun(a, b);
15891621
let expr_place = self.codegen_expr_to_place(p, res);
1590-
Stmt::block(vec![expr_place, check_stmt], loc)
1622+
Stmt::block(vec![check_stmt, expr_place], loc)
15911623
}
15921624

15931625
/// Intrinsics which encode a SIMD bitshift.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FAILURE\
2+
attempt to compute simd_div which would overflow
3+
UNREACHABLE\
4+
assertion failed: quotients.0 == quotients.1
5+
FAILURE\
6+
attempt to compute simd_rem which would overflow
7+
UNREACHABLE\
8+
assertion failed: remainders.0 == remainders.1
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Checks that the `simd_div` and `simd_rem` intrinsics check for integer overflows.
5+
6+
#![feature(repr_simd, platform_intrinsics)]
7+
8+
#[repr(simd)]
9+
#[allow(non_camel_case_types)]
10+
#[derive(Clone, Copy, PartialEq, Eq)]
11+
pub struct i32x2(i32, i32);
12+
13+
extern "platform-intrinsic" {
14+
fn simd_div<T>(x: T, y: T) -> T;
15+
fn simd_rem<T>(x: T, y: T) -> T;
16+
}
17+
18+
unsafe fn do_simd_div(dividends: i32x2, divisors: i32x2) -> i32x2 {
19+
simd_div(dividends, divisors)
20+
}
21+
22+
unsafe fn do_simd_rem(dividends: i32x2, divisors: i32x2) -> i32x2 {
23+
simd_rem(dividends, divisors)
24+
}
25+
26+
#[kani::proof]
27+
fn test_simd_div_overflow() {
28+
let dividend = i32::MIN;
29+
let dividends = i32x2(dividend, dividend);
30+
let divisor = -1;
31+
let divisors = i32x2(divisor, divisor);
32+
let quotients = unsafe { do_simd_div(dividends, divisors) };
33+
assert_eq!(quotients.0, quotients.1);
34+
}
35+
36+
#[kani::proof]
37+
fn test_simd_rem_overflow() {
38+
let dividend = i32::MIN;
39+
let dividends = i32x2(dividend, dividend);
40+
let divisor = -1;
41+
let divisors = i32x2(divisor, divisor);
42+
let remainders = unsafe { do_simd_rem(dividends, divisors) };
43+
assert_eq!(remainders.0, remainders.1);
44+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Checks that the `simd_div` intrinsic returns the expected results for floating point numbers.
5+
#![feature(repr_simd, platform_intrinsics)]
6+
7+
#[repr(simd)]
8+
#[allow(non_camel_case_types)]
9+
#[derive(Clone, Copy, PartialEq, kani::Arbitrary)]
10+
pub struct f32x2(f32, f32);
11+
12+
impl f32x2 {
13+
fn new_with(f: impl Fn() -> f32) -> Self {
14+
f32x2(f(), f())
15+
}
16+
17+
fn non_simd_div(self, divisors: Self) -> Self {
18+
f32x2(self.0 / divisors.0, self.1 / divisors.1)
19+
}
20+
}
21+
22+
extern "platform-intrinsic" {
23+
fn simd_div<T>(x: T, y: T) -> T;
24+
}
25+
26+
#[kani::proof]
27+
fn test_simd_div() {
28+
let dividends = f32x2::new_with(|| {
29+
let multiplier = kani::any_where(|&n: &i8| n >= -5 && n <= 5);
30+
0.5 * f32::from(multiplier)
31+
});
32+
let divisors = f32x2::new_with(|| {
33+
let multiplier = kani::any_where(|&n: &i8| n != 0 && n >= -5 && n <= 5);
34+
0.5 * f32::from(multiplier)
35+
});
36+
let normal_results = dividends.non_simd_div(divisors);
37+
let simd_results = unsafe { simd_div(dividends, divisors) };
38+
assert_eq!(normal_results, simd_results);
39+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! Checks that the `simd_rem` intrinsic returns the expected results for floating point numbers.
5+
#![feature(repr_simd, platform_intrinsics)]
6+
7+
#[repr(simd)]
8+
#[allow(non_camel_case_types)]
9+
#[derive(Clone, Copy, PartialEq, kani::Arbitrary)]
10+
pub struct f32x2(f32, f32);
11+
12+
impl f32x2 {
13+
fn new_with(f: impl Fn() -> f32) -> Self {
14+
f32x2(f(), f())
15+
}
16+
17+
fn non_simd_rem(self, divisors: Self) -> Self {
18+
f32x2(self.0 % divisors.0, self.1 % divisors.1)
19+
}
20+
}
21+
22+
extern "platform-intrinsic" {
23+
fn simd_rem<T>(x: T, y: T) -> T;
24+
}
25+
26+
#[kani::proof]
27+
fn test_simd_rem() {
28+
let dividends = f32x2::new_with(|| {
29+
let multiplier = kani::any_where(|&n: &i8| n >= -5 && n <= 5);
30+
0.5 * f32::from(multiplier)
31+
});
32+
let divisors = f32x2::new_with(|| {
33+
let multiplier = kani::any_where(|&n: &i8| n != 0 && n >= -5 && n <= 5);
34+
0.5 * f32::from(multiplier)
35+
});
36+
let normal_results = dividends.non_simd_rem(divisors);
37+
let simd_results = unsafe { simd_rem(dividends, divisors) };
38+
assert_eq!(normal_results, simd_results);
39+
}

0 commit comments

Comments
 (0)