Skip to content

Commit ce80a91

Browse files
committed
A GAT approach to bitmaps
Every time I look at the bitmap module, I get confused by what all those traits are trying to tell me. Replace the trait pattern that was common in Rust pre-GAT with actual GATs. I'd love to give motivation for this work, but by the time I was done with this commit, I forgot why I started looking into this. No functional change intended Signed-off-by: Patrick Roy <roypat@amazon.co.uk>
1 parent 05a85b3 commit ce80a91

File tree

5 files changed

+65
-57
lines changed

5 files changed

+65
-57
lines changed

src/bitmap/backend/atomic_bitmap.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use std::num::NonZeroUsize;
77
use std::sync::atomic::{AtomicU64, Ordering};
88

9-
use crate::bitmap::{Bitmap, RefSlice, WithBitmapSlice};
9+
use crate::bitmap::{Bitmap, RefSlice, Sliceable};
1010

1111
#[cfg(feature = "backend-mmap")]
1212
use crate::mmap::NewBitmap;
@@ -166,8 +166,8 @@ impl Clone for AtomicBitmap {
166166
}
167167
}
168168

169-
impl<'a> WithBitmapSlice<'a> for AtomicBitmap {
170-
type S = RefSlice<'a, Self>;
169+
impl Sliceable for AtomicBitmap {
170+
type Slice<'a> = RefSlice<'a, Self>;
171171
}
172172

173173
impl Bitmap for AtomicBitmap {
@@ -179,7 +179,7 @@ impl Bitmap for AtomicBitmap {
179179
self.is_addr_set(offset)
180180
}
181181

182-
fn slice_at(&self, offset: usize) -> <Self as WithBitmapSlice>::S {
182+
fn slice_at(&self, offset: usize) -> RefSlice<Self> {
183183
RefSlice::new(self, offset)
184184
}
185185
}

src/bitmap/backend/atomic_bitmap_arc.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use std::ops::Deref;
55
use std::sync::Arc;
66

7-
use crate::bitmap::{ArcSlice, AtomicBitmap, Bitmap, WithBitmapSlice};
7+
use crate::bitmap::{ArcSlice, AtomicBitmap, Bitmap, Sliceable};
88

99
#[cfg(feature = "backend-mmap")]
1010
use crate::mmap::NewBitmap;
@@ -41,8 +41,8 @@ impl Deref for AtomicBitmapArc {
4141
}
4242
}
4343

44-
impl WithBitmapSlice<'_> for AtomicBitmapArc {
45-
type S = ArcSlice<AtomicBitmap>;
44+
impl Sliceable for AtomicBitmapArc {
45+
type Slice<'a> = ArcSlice<AtomicBitmap>;
4646
}
4747

4848
impl Bitmap for AtomicBitmapArc {
@@ -54,7 +54,7 @@ impl Bitmap for AtomicBitmapArc {
5454
self.inner.is_addr_set(offset)
5555
}
5656

57-
fn slice_at(&self, offset: usize) -> <Self as WithBitmapSlice>::S {
57+
fn slice_at(&self, offset: usize) -> ArcSlice<AtomicBitmap> {
5858
ArcSlice::new(self.inner.clone(), offset)
5959
}
6060
}

src/bitmap/backend/slice.rs

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::fmt::{self, Debug};
77
use std::ops::Deref;
88
use std::sync::Arc;
99

10-
use crate::bitmap::{Bitmap, BitmapSlice, WithBitmapSlice};
10+
use crate::bitmap::{Bitmap, Sliceable};
1111

1212
/// Represents a slice into a `Bitmap` object, starting at `base_offset`.
1313
#[derive(Clone, Copy)]
@@ -26,25 +26,13 @@ impl<B> BaseSlice<B> {
2626
}
2727
}
2828

29-
impl<B> WithBitmapSlice<'_> for BaseSlice<B>
30-
where
31-
B: Clone + Deref,
32-
B::Target: Bitmap,
33-
{
34-
type S = Self;
35-
}
36-
37-
impl<B> BitmapSlice for BaseSlice<B>
38-
where
39-
B: Clone + Deref,
40-
B::Target: Bitmap,
41-
{
29+
impl<B: Deref<Target: Bitmap> + Clone> Sliceable for BaseSlice<B> {
30+
type Slice<'a> = BaseSlice<B>;
4231
}
4332

4433
impl<B> Bitmap for BaseSlice<B>
4534
where
46-
B: Clone + Deref,
47-
B::Target: Bitmap,
35+
B: Clone + Deref<Target: Bitmap>
4836
{
4937
/// Mark the memory range specified by the given `offset` (relative to the base offset of
5038
/// the slice) and `len` as dirtied.

src/bitmap/mod.rs

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,52 +9,76 @@
99
#[cfg(any(test, feature = "backend-bitmap"))]
1010
mod backend;
1111

12-
use std::fmt::Debug;
13-
1412
use crate::{GuestMemory, GuestMemoryRegion};
1513

1614
#[cfg(any(test, feature = "backend-bitmap"))]
1715
pub use backend::{ArcSlice, AtomicBitmap, RefSlice};
1816

19-
/// Trait implemented by types that support creating `BitmapSlice` objects.
20-
pub trait WithBitmapSlice<'a> {
21-
/// Type of the bitmap slice.
22-
type S: BitmapSlice;
17+
/// Trait for things that can be sliced.
18+
///
19+
/// Slicing can mean two things:
20+
/// 1. You have ownership of a bitmap, and slicing gives you a borrowed version of it (e.g.
21+
/// akin to `Vec<T> -> &'a [T]`)
22+
/// 2. You already have a borrowed version of a bitmap, and want to subslice it.
23+
///
24+
/// The trouble is that if we encode slicing at the type level, then each time a bitmap gets
25+
/// sliced, we get a new type - the return type of something like `bitmap.slice_at().slice_at()`
26+
/// would be `B::Slice<'a>::Slice<'b>`. Effectively, we're dealing with infinite types, which
27+
/// would make things like `VolatileSlice` that have a `B: Bitmap` parameter impossible to work
28+
/// with. To avoid this, the associated type has such a weird bound: It says that each subsequent
29+
/// slice of a slice must have the same type (including lifetimes) as the first slice. E.g.
30+
/// for all possible lifetimes `'b`, we have `B::Slice<'a>::Slice<'b> == B::Slice<'a>`.
31+
/// This then inductively collapses the type of subsequent `slice_at` calls to just be
32+
/// `B::Slice<'a>`. This simplification is based on the assumption that a function with the
33+
/// signature
34+
///
35+
/// ```no_run
36+
/// fn slice_at<'a, 'b, B: vm_memory::bitmap::Bitmap>(slice: &'b B::Slice<'a>) -> B::Slice<'a>
37+
/// where
38+
/// 'a: 'b { todo!() }
39+
/// ```
40+
///
41+
/// can always be written (note the returned value having a lifetime of `'a`, which outlives
42+
/// the lifetime `'b` of the reference passed into the function). Conceptually, this makes sense
43+
/// though: Slicing multiple times will return references to the object originally being sliced,
44+
/// so the lifetime should reflect this tie to the original bitmap being sliced, and not a tie
45+
/// to a slice of the original bitmap.
46+
///
47+
/// Note: This trait cannot be merged with [`Bitmap`] because of rust-lang#87479 (it would
48+
/// force a `where Self: 'a` bound onto the associated type, which is imposible to satisfy
49+
/// for implementors due to the higher-ranked trait bound).
50+
pub trait Sliceable {
51+
/// The type of the slice we get after calling [`Bitmap::slice_at`].
52+
type Slice<'a>: BitmapSlice;
2353
}
2454

25-
/// Trait used to represent that a `BitmapSlice` is a `Bitmap` itself, but also satisfies the
26-
/// restriction that slices created from it have the same type as `Self`.
27-
pub trait BitmapSlice: Bitmap + Clone + Debug + for<'a> WithBitmapSlice<'a, S = Self> {}
55+
pub trait BitmapSlice: for<'a> Bitmap<Slice<'a> = Self> where {}
2856

29-
/// Common bitmap operations. Using Higher-Rank Trait Bounds (HRTBs) to effectively define
30-
/// an associated type that has a lifetime parameter, without tagging the `Bitmap` trait with
31-
/// a lifetime as well.
57+
impl<B> BitmapSlice for B where B: for<'a> Bitmap<Slice<'a> = B> {}
58+
59+
/// Common bitmap operations.
3260
///
3361
/// Using an associated type allows implementing the `Bitmap` and `BitmapSlice` functionality
3462
/// as a zero-cost abstraction when providing trivial implementations such as the one
3563
/// defined for `()`.
3664
// These methods represent the core functionality that's required by `vm-memory` abstractions
3765
// to implement generic tracking logic, as well as tests that can be reused by different backends.
38-
pub trait Bitmap: for<'a> WithBitmapSlice<'a> {
66+
pub trait Bitmap: Sliceable + Clone {
3967
/// Mark the memory range specified by the given `offset` and `len` as dirtied.
4068
fn mark_dirty(&self, offset: usize, len: usize);
4169

4270
/// Check whether the specified `offset` is marked as dirty.
4371
fn dirty_at(&self, offset: usize) -> bool;
4472

45-
/// Return a `<Self as WithBitmapSlice>::S` slice of the current bitmap, starting at
73+
/// Return a `BitmapSlice` slice of the current bitmap, starting at
4674
/// the specified `offset`.
47-
fn slice_at(&self, offset: usize) -> <Self as WithBitmapSlice>::S;
75+
fn slice_at(&self, offset: usize) -> Self::Slice<'_>;
4876
}
4977

50-
/// A no-op `Bitmap` implementation that can be provided for backends that do not actually
51-
/// require the tracking functionality.
52-
impl WithBitmapSlice<'_> for () {
53-
type S = Self;
78+
impl Sliceable for () {
79+
type Slice<'a> = ();
5480
}
5581

56-
impl BitmapSlice for () {}
57-
5882
impl Bitmap for () {
5983
fn mark_dirty(&self, _offset: usize, _len: usize) {}
6084

@@ -65,17 +89,12 @@ impl Bitmap for () {
6589
fn slice_at(&self, _offset: usize) -> Self {}
6690
}
6791

68-
/// A `Bitmap` and `BitmapSlice` implementation for `Option<B>`.
69-
impl<'a, B> WithBitmapSlice<'a> for Option<B>
70-
where
71-
B: WithBitmapSlice<'a>,
72-
{
73-
type S = Option<B::S>;
92+
impl<B: Sliceable> Sliceable for Option<B> {
93+
type Slice<'a> = Option<B::Slice<'a>>;
7494
}
7595

76-
impl<B: BitmapSlice> BitmapSlice for Option<B> {}
77-
78-
impl<B: Bitmap> Bitmap for Option<B> {
96+
impl<B: Bitmap> Bitmap for Option<B>
97+
{
7998
fn mark_dirty(&self, offset: usize, len: usize) {
8099
if let Some(inner) = self {
81100
inner.mark_dirty(offset, len)
@@ -89,7 +108,7 @@ impl<B: Bitmap> Bitmap for Option<B> {
89108
false
90109
}
91110

92-
fn slice_at(&self, offset: usize) -> Option<<B as WithBitmapSlice>::S> {
111+
fn slice_at(&self, offset: usize) -> Self::Slice<'_> {
93112
if let Some(inner) = self {
94113
return Some(inner.slice_at(offset));
95114
}
@@ -99,14 +118,15 @@ impl<B: Bitmap> Bitmap for Option<B> {
99118

100119
/// Helper type alias for referring to the `BitmapSlice` concrete type associated with
101120
/// an object `B: WithBitmapSlice<'a>`.
102-
pub type BS<'a, B> = <B as WithBitmapSlice<'a>>::S;
121+
pub type BS<'a, B> = <B as Sliceable>::Slice<'a>;
103122

104123
/// Helper type alias for referring to the `BitmapSlice` concrete type associated with
105124
/// the memory regions of an object `M: GuestMemory`.
106125
pub type MS<'a, M> = BS<'a, <<M as GuestMemory>::R as GuestMemoryRegion>::B>;
107126

108127
#[cfg(test)]
109128
pub(crate) mod tests {
129+
use std::fmt::Debug;
110130
use super::*;
111131

112132
use std::io::Cursor;

src/volatile_memory.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -999,7 +999,7 @@ impl<B: BitmapSlice> VolatileMemory for VolatileSlice<'_, B> {
999999
self.size
10001000
}
10011001

1002-
fn get_slice(&self, offset: usize, count: usize) -> Result<VolatileSlice<B>> {
1002+
fn get_slice(&self, offset: usize, count: usize) -> Result<Self> {
10031003
let _ = self.compute_end_offset(offset, count)?;
10041004
Ok(
10051005
// SAFETY: This is safe because the pointer is range-checked by compute_end_offset, and

0 commit comments

Comments
 (0)