Skip to content

Commit 2521805

Browse files
committed
kani: improve performance
The `verify_read_from_iovec` and especially `verify_write_from_iovec` harnesses started taking upwards of 30 minutes for verification. Optimize them down to take less than 1 minute without losing generality. Achieve this by applying something akin to loop-unrolling: Instead of having a `let nr_desc: usize = kani::any_where(...)` that limits `nr_desc` to be at most a fixed constant, use a for loop to explicitly list all possibilities up to said constant. Signed-off-by: Patrick Roy <[email protected]>
1 parent eb4540c commit 2521805

File tree

1 file changed

+61
-55
lines changed

1 file changed

+61
-55
lines changed

src/vmm/src/devices/virtio/iovec.rs

Lines changed: 61 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -690,8 +690,7 @@ mod verification {
690690
// >= 1.
691691
const MAX_DESC_LENGTH: usize = 4;
692692

693-
fn create_iovecs(mem: *mut u8, size: usize) -> (IoVecVec, u32) {
694-
let nr_descs: usize = kani::any_where(|&n| n <= MAX_DESC_LENGTH);
693+
fn create_iovecs(mem: *mut u8, size: usize, nr_descs: usize) -> (IoVecVec, u32) {
695694
let mut vecs: Vec<iovec> = Vec::with_capacity(nr_descs);
696695
let mut len = 0u32;
697696
for _ in 0..nr_descs {
@@ -711,18 +710,18 @@ mod verification {
711710
(vecs, len)
712711
}
713712

714-
impl kani::Arbitrary for IoVecBuffer {
715-
fn any() -> Self {
713+
impl IoVecBuffer {
714+
fn any_of_length(nr_descs: usize) -> Self {
716715
// We only read from `IoVecBuffer`, so create here a guest memory object, with arbitrary
717716
// contents and size up to GUEST_MEMORY_SIZE.
718717
let mut mem = ManuallyDrop::new(kani::vec::exact_vec::<u8, GUEST_MEMORY_SIZE>());
719-
let (vecs, len) = create_iovecs(mem.as_mut_ptr(), mem.len());
718+
let (vecs, len) = create_iovecs(mem.as_mut_ptr(), mem.len(), nr_descs);
720719
Self { vecs, len }
721720
}
722721
}
723722

724-
impl kani::Arbitrary for IoVecBufferMut {
725-
fn any() -> Self {
723+
impl IoVecBufferMut {
724+
fn any_of_length(nr_descs: usize) -> Self {
726725
// We only write into `IoVecBufferMut` objects, so we can simply create a guest memory
727726
// object initialized to zeroes, trying to be nice to Kani.
728727
let mem = unsafe {
@@ -732,7 +731,7 @@ mod verification {
732731
))
733732
};
734733

735-
let (vecs, len) = create_iovecs(mem, GUEST_MEMORY_SIZE);
734+
let (vecs, len) = create_iovecs(mem, GUEST_MEMORY_SIZE, nr_descs);
736735
Self { vecs, len }
737736
}
738737
}
@@ -777,58 +776,65 @@ mod verification {
777776
#[kani::unwind(5)]
778777
#[kani::solver(cadical)]
779778
fn verify_read_from_iovec() {
780-
let iov: IoVecBuffer = kani::any();
781-
782-
let mut buf = vec![0; GUEST_MEMORY_SIZE];
783-
let offset: u32 = kani::any();
784-
785-
// We can't really check the contents that the operation here writes into `buf`, because
786-
// our `IoVecBuffer` being completely arbitrary can contain overlapping memory regions, so
787-
// checking the data copied is not exactly trivial.
788-
//
789-
// What we can verify is the bytes that we read out from guest memory:
790-
// - `buf.len()`, if `offset + buf.len() < iov.len()`;
791-
// - `iov.len() - offset`, otherwise.
792-
// Furthermore, we know our Read-/WriteVolatile implementation above is infallible, so
793-
// provided that the logic inside read_volatile_at is correct, we should always get Ok(...)
794-
assert_eq!(
795-
iov.read_volatile_at(
796-
&mut KaniBuffer(&mut buf),
797-
offset as usize,
798-
GUEST_MEMORY_SIZE
799-
)
800-
.unwrap(),
801-
buf.len().min(iov.len().saturating_sub(offset) as usize)
802-
);
779+
for nr_descs in 0..MAX_DESC_LENGTH {
780+
let iov = IoVecBuffer::any_of_length(nr_descs);
781+
782+
let mut buf = vec![0; GUEST_MEMORY_SIZE];
783+
let offset: u32 = kani::any();
784+
785+
// We can't really check the contents that the operation here writes into `buf`, because
786+
// our `IoVecBuffer` being completely arbitrary can contain overlapping memory regions,
787+
// so checking the data copied is not exactly trivial.
788+
//
789+
// What we can verify is the bytes that we read out from guest memory:
790+
// - `buf.len()`, if `offset + buf.len() < iov.len()`;
791+
// - `iov.len() - offset`, otherwise.
792+
// Furthermore, we know our Read-/WriteVolatile implementation above is infallible, so
793+
// provided that the logic inside read_volatile_at is correct, we should always get
794+
// Ok(...)
795+
assert_eq!(
796+
iov.read_volatile_at(
797+
&mut KaniBuffer(&mut buf),
798+
offset as usize,
799+
GUEST_MEMORY_SIZE
800+
)
801+
.unwrap(),
802+
buf.len().min(iov.len().saturating_sub(offset) as usize)
803+
);
804+
}
803805
}
804806

805807
#[kani::proof]
806808
#[kani::unwind(5)]
807809
#[kani::solver(cadical)]
808810
fn verify_write_to_iovec() {
809-
let mut iov_mut: IoVecBufferMut = kani::any();
810-
811-
let mut buf = kani::vec::any_vec::<u8, GUEST_MEMORY_SIZE>();
812-
let offset: u32 = kani::any();
813-
814-
// We can't really check the contents that the operation here writes into `IoVecBufferMut`,
815-
// because our `IoVecBufferMut` being completely arbitrary can contain overlapping memory
816-
// regions, so checking the data copied is not exactly trivial.
817-
//
818-
// What we can verify is the bytes that we write into guest memory:
819-
// - `buf.len()`, if `offset + buf.len() < iov.len()`;
820-
// - `iov.len() - offset`, otherwise.
821-
// Furthermore, we know our Read-/WriteVolatile implementation above is infallible, so
822-
// provided that the logic inside write_volatile_at is correct, we should always get Ok(...)
823-
assert_eq!(
824-
iov_mut
825-
.write_volatile_at(
826-
&mut KaniBuffer(&mut buf),
827-
offset as usize,
828-
GUEST_MEMORY_SIZE
829-
)
830-
.unwrap(),
831-
buf.len().min(iov_mut.len().saturating_sub(offset) as usize)
832-
);
811+
for nr_descs in 0..MAX_DESC_LENGTH {
812+
let mut iov_mut = IoVecBufferMut::any_of_length(nr_descs);
813+
814+
let mut buf = kani::vec::any_vec::<u8, GUEST_MEMORY_SIZE>();
815+
let offset: u32 = kani::any();
816+
817+
// We can't really check the contents that the operation here writes into
818+
// `IoVecBufferMut`, because our `IoVecBufferMut` being completely arbitrary
819+
// can contain overlapping memory regions, so checking the data copied is
820+
// not exactly trivial.
821+
//
822+
// What we can verify is the bytes that we write into guest memory:
823+
// - `buf.len()`, if `offset + buf.len() < iov.len()`;
824+
// - `iov.len() - offset`, otherwise.
825+
// Furthermore, we know our Read-/WriteVolatile implementation above is infallible, so
826+
// provided that the logic inside write_volatile_at is correct, we should always get
827+
// Ok(...)
828+
assert_eq!(
829+
iov_mut
830+
.write_volatile_at(
831+
&mut KaniBuffer(&mut buf),
832+
offset as usize,
833+
GUEST_MEMORY_SIZE
834+
)
835+
.unwrap(),
836+
buf.len().min(iov_mut.len().saturating_sub(offset) as usize)
837+
);
838+
}
833839
}
834840
}

0 commit comments

Comments
 (0)