Skip to content

Commit b62d1d9

Browse files
authored
Merge pull request #462 from Freax13/enhancement/faster-address-fns
optimize `Step` impl for `VirtAddr`
2 parents 4f95c56 + 0448ade commit b62d1d9

File tree

2 files changed

+175
-6
lines changed

2 files changed

+175
-6
lines changed

.github/workflows/build.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,13 @@ jobs:
233233
- run: cargo install cargo-semver-checks --locked
234234
- name: Check semver
235235
run: cargo +stable semver-checks check-release
236+
237+
kani:
238+
runs-on: ubuntu-20.04
239+
steps:
240+
- uses: actions/checkout@v4
241+
- uses: Swatinem/rust-cache@v2
242+
with:
243+
shared-key: "kani"
244+
cache-targets: false
245+
- uses: model-checking/kani-github-action@v1.1

src/addr.rs

Lines changed: 165 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,8 @@ impl VirtAddr {
229229
pub(crate) fn steps_between_impl(start: &Self, end: &Self) -> Option<usize> {
230230
let mut steps = end.0.checked_sub(start.0)?;
231231

232-
// Check if we jumped the gap.
233-
if end.0.get_bit(47) && !start.0.get_bit(47) {
234-
steps = steps.checked_sub(0xffff_0000_0000_0000).unwrap();
235-
}
232+
// Mask away extra bits that appear while jumping the gap.
233+
steps &= 0xffff_ffff_ffff;
236234

237235
usize::try_from(steps).ok()
238236
}
@@ -258,7 +256,7 @@ impl VirtAddr {
258256
_ => {}
259257
}
260258

261-
Some(Self::new(addr))
259+
Some(unsafe { Self::new_unsafe(addr) })
262260
}
263261
}
264262

@@ -373,7 +371,7 @@ impl Step for VirtAddr {
373371
_ => {}
374372
}
375373

376-
Some(Self::new(addr))
374+
Some(unsafe { Self::new_unsafe(addr) })
377375
}
378376
}
379377

@@ -784,3 +782,164 @@ mod tests {
784782
assert_eq!(VirtAddr::from_ptr(slice), VirtAddr::from_ptr(&slice[0]));
785783
}
786784
}
785+
786+
#[cfg(kani)]
787+
mod proofs {
788+
use super::*;
789+
790+
// The next two proof harnesses prove the correctness of the `forward`
791+
// implementation of VirtAddr.
792+
793+
// This harness proves that our implementation can correctly take 0 or 1
794+
// step starting from any address.
795+
#[kani::proof]
796+
fn forward_base_case() {
797+
let start_raw: u64 = kani::any();
798+
let Ok(start) = VirtAddr::try_new(start_raw) else {
799+
return;
800+
};
801+
802+
// Adding 0 to any address should always yield the same address.
803+
let same = Step::forward(start, 0);
804+
assert!(start == same);
805+
806+
// Manually calculate the expected address after stepping once.
807+
let expected = match start_raw {
808+
// Adding 1 to addresses in this range don't require gap jumps, so
809+
// we can just add 1.
810+
0x0000_0000_0000_0000..=0x0000_7fff_ffff_fffe => Some(start_raw + 1),
811+
// Adding 1 to this address jumps the gap.
812+
0x0000_7fff_ffff_ffff => Some(0xffff_8000_0000_0000),
813+
// The range of non-canonical addresses.
814+
0x0000_8000_0000_0000..=0xffff_7fff_ffff_ffff => unreachable!(),
815+
// Adding 1 to addresses in this range don't require gap jumps, so
816+
// we can just add 1.
817+
0xffff_8000_0000_0000..=0xffff_ffff_ffff_fffe => Some(start_raw + 1),
818+
// Adding 1 to this address causes an overflow.
819+
0xffff_ffff_ffff_ffff => None,
820+
};
821+
if let Some(expected) = expected {
822+
// Verify that `expected` is a valid address.
823+
assert!(VirtAddr::try_new(expected).is_ok());
824+
}
825+
// Verify `forward_checked`.
826+
let next = Step::forward_checked(start, 1);
827+
assert!(next.map(VirtAddr::as_u64) == expected);
828+
}
829+
830+
// This harness proves that the result of taking two small steps is the
831+
// same as taking one combined large step.
832+
#[kani::proof]
833+
fn forward_induction_step() {
834+
let start_raw: u64 = kani::any();
835+
let Ok(start) = VirtAddr::try_new(start_raw) else {
836+
return;
837+
};
838+
839+
let count1: usize = kani::any();
840+
let count2: usize = kani::any();
841+
// If we can take two small steps...
842+
let Some(next1) = Step::forward_checked(start, count1) else {
843+
return;
844+
};
845+
let Some(next2) = Step::forward_checked(next1, count2) else {
846+
return;
847+
};
848+
849+
// ...then we can also take one combined large step.
850+
let count_both = count1 + count2;
851+
let next_both = Step::forward(start, count_both);
852+
assert!(next2 == next_both);
853+
}
854+
855+
// The next two proof harnesses prove the correctness of the `backward`
856+
// implementation of VirtAddr using the `forward` implementation which
857+
// we've already proven to be correct.
858+
// They do this by proving the symmetry between those two functions.
859+
860+
// This harness proves the correctness of the implementation of `backward`
861+
// for all inputs for which `forward_checked` succeeds.
862+
#[kani::proof]
863+
fn forward_implies_backward() {
864+
let start_raw: u64 = kani::any();
865+
let Ok(start) = VirtAddr::try_new(start_raw) else {
866+
return;
867+
};
868+
let count: usize = kani::any();
869+
870+
// If `forward_checked` succeeds...
871+
let Some(end) = Step::forward_checked(start, count) else {
872+
return;
873+
};
874+
875+
// ...then `backward` succeeds as well.
876+
let start2 = Step::backward(end, count);
877+
assert!(start == start2);
878+
}
879+
880+
// This harness proves that for all inputs for which `backward_checked`
881+
// succeeds, `forward` succeeds as well.
882+
#[kani::proof]
883+
fn backward_implies_forward() {
884+
let end_raw: u64 = kani::any();
885+
let Ok(end) = VirtAddr::try_new(end_raw) else {
886+
return;
887+
};
888+
let count: usize = kani::any();
889+
890+
// If `backward_checked` succeeds...
891+
let Some(start) = Step::backward_checked(end, count) else {
892+
return;
893+
};
894+
895+
// ...then `forward` succeeds as well.
896+
let end2 = Step::forward(start, count);
897+
assert!(end == end2);
898+
}
899+
900+
// The next two proof harnesses prove the correctness of the
901+
// `steps_between` implementation of VirtAddr using the `forward`
902+
// implementation which we've already proven to be correct.
903+
// They do this by proving the symmetry between those two functions.
904+
905+
// This harness proves the correctness of the implementation of
906+
// `steps_between` for all inputs for which `forward_checked` succeeds.
907+
#[kani::proof]
908+
fn forward_implies_steps_between() {
909+
let start: u64 = kani::any();
910+
let Ok(start) = VirtAddr::try_new(start) else {
911+
return;
912+
};
913+
let count: usize = kani::any();
914+
915+
// If `forward_checked` succeeds...
916+
let Some(end) = Step::forward_checked(start, count) else {
917+
return;
918+
};
919+
920+
// ...then `steps_between` succeeds as well.
921+
assert!(Step::steps_between(&start, &end) == Some(count));
922+
}
923+
924+
// This harness proves that for all inputs for which `steps_between`
925+
// succeeds, `forward` succeeds as well.
926+
#[kani::proof]
927+
fn steps_between_implies_forward() {
928+
let start: u64 = kani::any();
929+
let Ok(start) = VirtAddr::try_new(start) else {
930+
return;
931+
};
932+
let end: u64 = kani::any();
933+
let Ok(end) = VirtAddr::try_new(end) else {
934+
return;
935+
};
936+
937+
// If `steps_between` succeeds...
938+
let Some(count) = Step::steps_between(&start, &end) else {
939+
return;
940+
};
941+
942+
// ...then `forward` succeeds as well.
943+
assert!(Step::forward(start, count) == end);
944+
}
945+
}

0 commit comments

Comments
 (0)