Skip to content

Commit 2e57044

Browse files
committed
[Boogie Backend] Add support for int-to-int casts (#2957)
Add support for int-to-int cast operations (MIR's `Rvalue::Cast` with the `CastKind::IntToInt` kind) through implementing them as extracts/concats in Boogie. The PR also includes another unbounded verification example involving the serialization of an array of words into a buffer of bytes, which involves int-to-int cast operations. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 and MIT licenses.
1 parent 8ea0e00 commit 2e57044

File tree

7 files changed

+189
-1
lines changed

7 files changed

+189
-1
lines changed

boogie_ast/src/boogie_program/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ pub enum BinaryOp {
138138

139139
/// Modulo
140140
Mod,
141+
142+
/// Bit-Vector Concat
143+
Concat,
141144
}
142145

143146
/// Expr types
@@ -155,6 +158,9 @@ pub enum Expr {
155158
/// Binary operation
156159
BinaryOp { op: BinaryOp, left: Box<Expr>, right: Box<Expr> },
157160

161+
/// Bit-vector extract operation, e.g. `x[7:2]` (half-open interval)
162+
Extract { base: Box<Expr>, high: usize, low: usize },
163+
158164
/// Function call
159165
FunctionCall { symbol: String, arguments: Vec<Expr> },
160166

@@ -170,9 +176,26 @@ impl Expr {
170176
Expr::Literal(l)
171177
}
172178

179+
pub fn concat(e1: Box<Expr>, e2: Box<Expr>) -> Self {
180+
Expr::BinaryOp { op: BinaryOp::Concat, left: e1, right: e2 }
181+
}
182+
183+
pub fn extract(base: Box<Expr>, high: usize, low: usize) -> Self {
184+
Expr::Extract { base, high, low }
185+
}
186+
173187
pub fn function_call(symbol: String, arguments: Vec<Expr>) -> Self {
174188
Expr::FunctionCall { symbol, arguments }
175189
}
190+
191+
pub fn sign_extend(e: Box<Expr>, _width: usize) -> Self {
192+
let _e = e;
193+
todo!()
194+
}
195+
196+
pub fn zero_extend(e: Box<Expr>, width: usize) -> Self {
197+
Expr::concat(Box::new(Expr::literal(Literal::bv(width, 0.into()))), e)
198+
}
176199
}
177200

178201
impl ToString for Expr {

boogie_ast/src/boogie_program/writer.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,11 @@ impl Expr {
300300
right.write_to(writer)?;
301301
write!(writer, ")")?;
302302
}
303+
Expr::Extract { base, high, low } => {
304+
write!(writer, "(")?;
305+
base.write_to(writer)?;
306+
write!(writer, ")[{high}:{low}]")?;
307+
}
303308
Expr::FunctionCall { symbol, arguments } => {
304309
write!(writer, "{symbol}(")?;
305310
for (i, a) in arguments.iter().enumerate() {
@@ -521,6 +526,7 @@ impl BinaryOp {
521526
BinaryOp::Gt => write!(writer, ">")?,
522527
BinaryOp::Lte => write!(writer, "<=")?,
523528
BinaryOp::Gte => write!(writer, ">=")?,
529+
BinaryOp::Concat => write!(writer, "++")?,
524530
}
525531
Ok(())
526532
}

kani-compiler/src/codegen_boogie/context/boogie_ctx.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

44
use itertools::Itertools;
5+
use std::cmp::Ordering;
56
use std::io::Write;
67

78
use crate::kani_queries::QueryDb;
@@ -13,7 +14,7 @@ use rustc_data_structures::fx::FxHashMap;
1314
use rustc_middle::mir::interpret::Scalar;
1415
use rustc_middle::mir::traversal::reverse_postorder;
1516
use rustc_middle::mir::{
16-
BasicBlock, BasicBlockData, BinOp, Body, Const as mirConst, ConstOperand, ConstValue,
17+
BasicBlock, BasicBlockData, BinOp, Body, CastKind, Const as mirConst, ConstOperand, ConstValue,
1718
HasLocalDecls, Local, Operand, Place, ProjectionElem, Rvalue, Statement, StatementKind,
1819
SwitchTargets, Terminator, TerminatorKind, UnOp, VarDebugInfoContents,
1920
};
@@ -321,6 +322,28 @@ impl<'a, 'tcx> FunctionCtx<'a, 'tcx> {
321322
// TODO: handle overflow check
322323
self.codegen_binary_op(binop, e1, e2)
323324
}
325+
Rvalue::Cast(kind, operand, ty) => {
326+
if *kind == CastKind::IntToInt {
327+
let from_type = self.operand_ty(operand);
328+
let o = self.codegen_operand(operand);
329+
let from = self.codegen_type(from_type);
330+
let to = self.codegen_type(*ty);
331+
let Type::Bv(from_width) = from else { panic!("Expecting bv type in cast") };
332+
let Type::Bv(to_width) = to else { panic!("Expecting bv type in cast") };
333+
let op = match from_width.cmp(&to_width) {
334+
Ordering::Greater => Expr::extract(Box::new(o), to_width, 0),
335+
Ordering::Less => match from_type.kind() {
336+
ty::Int(_) => Expr::sign_extend(Box::new(o), to_width - from_width),
337+
ty::Uint(_) => Expr::zero_extend(Box::new(o), to_width - from_width),
338+
_ => todo!(),
339+
},
340+
Ordering::Equal => o,
341+
};
342+
(None, op)
343+
} else {
344+
todo!()
345+
}
346+
}
324347
_ => todo!(),
325348
}
326349
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright Kani Contributors
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
script: gen_boogie_and_dump.sh
4+
expected: expected
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Datatypes:
2+
datatype $Array<T> { $Array(data: [bv64]T, len: bv64) }
3+
4+
// Functions:
5+
function {:bvbuiltin "bvult"} $BvUnsignedLessThan<T>(lhs: T, rhs: T) returns (bool);
6+
7+
function {:bvbuiltin "bvslt"} $BvSignedLessThan<T>(lhs: T, rhs: T) returns (bool);
8+
9+
function {:bvbuiltin "bvugt"} $BvUnsignedGreaterThan<T>(lhs: T, rhs: T) returns (bool);
10+
11+
function {:bvbuiltin "bvsgt"} $BvSignedGreaterThan<T>(lhs: T, rhs: T) returns (bool);
12+
13+
function {:bvbuiltin "bvadd"} $BvAdd<T>(lhs: T, rhs: T) returns (T);
14+
15+
function {:bvbuiltin "bvor"} $BvOr<T>(lhs: T, rhs: T) returns (T);
16+
17+
function {:bvbuiltin "bvand"} $BvAnd<T>(lhs: T, rhs: T) returns (T);
18+
19+
function {:bvbuiltin "bvshl"} $BvShl<T>(lhs: T, rhs: T) returns (T);
20+
21+
function {:bvbuiltin "bvlshr"} $BvShr<T>(lhs: T, rhs: T) returns (T);
22+
23+
// Procedures:
24+
procedure
25+
{
26+
var src: $Array bv16;
27+
var buf: $Array bv8;
28+
var src_len: bv64;
29+
var buf_len: bv64;
30+
var i: bv64;
31+
var x: bv16;
32+
var byte0: bv8;
33+
var byte1: bv8;
34+
var j: bv64;
35+
var dst: $Array bv16;
36+
bb
37+
havoc src;
38+
goto bb
39+
havoc buf;
40+
src_len := src->len;
41+
buf_len := buf->len;
42+
:= $BvShr(buf_len, 1bv64);
43+
:= !($BvUnsignedLessThan(
44+
assume
45+
i := 0bv64;
46+
:= $BvUnsignedLessThan(
47+
if (\
48+
goto bb\
49+
} else {
50+
goto bb\
51+
}
52+
havoc dst;
53+
return;
54+
:= src->data[
55+
:= dst->data[
56+
assert
57+
:= $BvAdd(i, 1bv64);
58+
:= $BvShl(
59+
:= buf->data[
60+
:= (0bv8 ++
61+
:= $BvOr(
62+
:= $BvAnd(x, 255bv16);
63+
)[8:0];
64+
:= $BvShr(x, 8bv16);
65+
byte1 :=
66+
j := $BvShl(
67+
buf->data[(j)] := byte0;
68+
:= $BvAdd(j, 1bv64);
69+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env bash
2+
# Copyright Kani Contributors
3+
# SPDX-License-Identifier: Apache-2.0 OR MIT
4+
5+
set +e
6+
7+
# Delete any leftover Boogie files
8+
rm *.bpl
9+
10+
echo "[TEST] Run verification..."
11+
kani -Zboogie test.rs
12+
13+
cat *.bpl
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
//! A test that demonstrates unbounded verification of array-based programs.
5+
//! The test uses `any_array` which creates arrays with non-deterministic
6+
//! content and length.
7+
//! The `src` array of words is serialized into the `buf` byte array and then
8+
//! deserialized back into `dst`.
9+
//! The test checks that the round trip of serialization and deserialization is
10+
//! the identity.
11+
12+
#[kani::proof]
13+
fn serde() {
14+
let src = kani::array::any_array::<u16>();
15+
let mut buf = kani::array::any_array::<u8>();
16+
let src_len: usize = src.len();
17+
let buf_len: usize = buf.len();
18+
kani::assume(buf_len >> 1u64 >= src_len);
19+
20+
// serialize
21+
let mut i: usize = 0;
22+
//Loop_invariant: forall j: usize :: j < i => ((buf[j << 1u64 + 1] ++ buf[j << 1u64]) == src[j])
23+
while i < src_len {
24+
let x = src[i];
25+
let byte0: u8 = (x & 0xFF) as u8;
26+
let byte1: u8 = ((x >> 8u16) & 0xFF) as u8;
27+
let j: usize = i << 1u64;
28+
buf[j] = byte0;
29+
buf[j + 1] = byte1;
30+
i += 1;
31+
}
32+
33+
// deserialize
34+
let mut dst = kani::array::any_array::<u16>();
35+
kani::assume(dst.len() >= src_len);
36+
i = 0;
37+
//Loop_invariant: forall j: usize :: j < i => ((buf[j << 1u64 + 1] ++ buf[j << 1u64]) == dst[j])
38+
while i < src_len {
39+
let j: usize = i << 1u64;
40+
dst[i] = ((buf[j + 1] as u16) << 8u16) | (buf[j] as u16);
41+
i += 1;
42+
}
43+
44+
// Check the round trip
45+
i = 0;
46+
while i < src_len {
47+
kani::assert(src[i] == dst[i], "serialization/deserialization failed");
48+
i += 1;
49+
}
50+
}

0 commit comments

Comments
 (0)