-
Notifications
You must be signed in to change notification settings - Fork 95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add explicit atomic (and aligned) operations #104
Conversation
What is the specific reason for removing the automatic implementation of Bytes? That is, which methods are meant to be overridden by different GuestMemory implementation? Regarding read/write_atomic, I would prefer to have an ordering argument and use atomics instead of volatile accesses, but the overall concept is great. I think we should add an Error for misaligned containers so that the methods can be added directly to Bytes. Can you also document how read/write_atomic compare to VolatileAtomicRef? Is it just different ergonomics just like VolatileRef vs.read/write_obj? |
Hi Paolo, thanks for the comment! I just want to try out a couple more ideas (including the things you mentioned) and then I'll update the PR and provide a complete answer. |
Hi again, the PR is updated. I've added an
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly looks great, only some stylistic choices to discuss (saving the flames for the blanket implementation PR :)).
|
||
impl<Addr: Address, T> Aligned<Addr, T> { | ||
/// Attempt to create an `Aligned` value based on `addr`. | ||
pub fn from_addr(addr: Addr) -> result::Result<Self, AlignmentError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these two functions be implementations of std::convert::TryFrom
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from_addr
should be a TryFrom
implementation, but it conflicts with a strange internal automatic implementation defined here (I think). Replacing cast
's current form with something like impl<A, T> TryFrom<Aligned<A,T>> for Aligned<A,T>
doesn't work because it also conflicts with some auto implementation defined somewhere :(
/// Wraps a `GuestAddress` that's known to be aligned with respect to `T`. | ||
pub type AlignedGuestAddress<T> = Aligned<GuestAddress, T>; | ||
|
||
impl<T> std::convert::TryFrom<GuestAddress> for AlignedGuestAddress<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implementing impl<Addr, T> TryFrom<Addr> for Aligned<Addr, T>
should make these two more specific implementations unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I didn't manage to get that to work, like I mentioned during a previous comment :(
@@ -117,11 +119,33 @@ impl Display for Error { | |||
pub struct GuestAddress(pub u64); | |||
impl_address_ops!(GuestAddress, u64); | |||
|
|||
/// Wraps a `GuestAddress` that's known to be aligned with respect to `T`. | |||
pub type AlignedGuestAddress<T> = Aligned<GuestAddress, T>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these two types useful?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They seemed nicer to use than the fully qualified Aligned<X, Y>
, but I don't have any strong feelings here.
pub struct Aligned<Addr, T> { | ||
addr: Addr, | ||
phantom: PhantomData<T>, | ||
} | ||
|
||
// Implementing `Clone` and `Copy` manually because deriving them did not work well |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it work if you use a PhantomData<*mut const T>
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a neat suggestion, but unfortunately doesn't seem to work ... I imagine the background logic sees a T
that's not necessary Copy
as part of the type signature, and it simply gives up at that point. However, your comment reminded me of this interesting provision regarding PhantomData
, so I switched to PhantomData<*const T>
anyway.
pub struct GuestAddress(pub u64); | ||
|
||
impl From<u64> for GuestAddress { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these should be implemented automatically by impl_address_ops!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is implemented to satisfy the newly added From<<Self as AddressValue>::V>
trait bound for Address
, which I found necessary precisely because I couldn't find a way to get the inner value using logic implemented as part of the impl_address_ops!
macro :(
For example, the macro previously expected to only be called for newtypes (i.e. GuestAddress(u64)
), so it could implement raw_value
automatically as self.0
. However, when I wanted to use the same macro for regular types (i.e. to implement Address
for usize
), I couldn't find a way to abstract obtaining the raw value that worked for both situations (and that didn't break various macro hygiene rules), so I've added the above trait bound. If there's a way to implement the conversion as part of the macro, then implementing From/Into
would no longer be necessary.
src/guest_memory.rs
Outdated
} | ||
} | ||
|
||
impl Into<u64> for GuestAddress { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And Into should probably never be implemented, instead you should implement From<GuestAddress> for u64
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure
pub struct MemoryRegionAddress(pub u64); | ||
|
||
impl From<u64> for MemoryRegionAddress { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I clicked "approve" by mistake.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay in answering. I'll post an extra commit with tentative Aligned
conversion improvements soon.
|
||
impl<Addr: Address, T> Aligned<Addr, T> { | ||
/// Attempt to create an `Aligned` value based on `addr`. | ||
pub fn from_addr(addr: Addr) -> result::Result<Self, AlignmentError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from_addr
should be a TryFrom
implementation, but it conflicts with a strange internal automatic implementation defined here (I think). Replacing cast
's current form with something like impl<A, T> TryFrom<Aligned<A,T>> for Aligned<A,T>
doesn't work because it also conflicts with some auto implementation defined somewhere :(
@@ -117,11 +119,33 @@ impl Display for Error { | |||
pub struct GuestAddress(pub u64); | |||
impl_address_ops!(GuestAddress, u64); | |||
|
|||
/// Wraps a `GuestAddress` that's known to be aligned with respect to `T`. | |||
pub type AlignedGuestAddress<T> = Aligned<GuestAddress, T>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They seemed nicer to use than the fully qualified Aligned<X, Y>
, but I don't have any strong feelings here.
/// Wraps a `GuestAddress` that's known to be aligned with respect to `T`. | ||
pub type AlignedGuestAddress<T> = Aligned<GuestAddress, T>; | ||
|
||
impl<T> std::convert::TryFrom<GuestAddress> for AlignedGuestAddress<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I didn't manage to get that to work, like I mentioned during a previous comment :(
pub struct Aligned<Addr, T> { | ||
addr: Addr, | ||
phantom: PhantomData<T>, | ||
} | ||
|
||
// Implementing `Clone` and `Copy` manually because deriving them did not work well |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a neat suggestion, but unfortunately doesn't seem to work ... I imagine the background logic sees a T
that's not necessary Copy
as part of the type signature, and it simply gives up at that point. However, your comment reminded me of this interesting provision regarding PhantomData
, so I switched to PhantomData<*const T>
anyway.
pub struct GuestAddress(pub u64); | ||
|
||
impl From<u64> for GuestAddress { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is implemented to satisfy the newly added From<<Self as AddressValue>::V>
trait bound for Address
, which I found necessary precisely because I couldn't find a way to get the inner value using logic implemented as part of the impl_address_ops!
macro :(
For example, the macro previously expected to only be called for newtypes (i.e. GuestAddress(u64)
), so it could implement raw_value
automatically as self.0
. However, when I wanted to use the same macro for regular types (i.e. to implement Address
for usize
), I couldn't find a way to abstract obtaining the raw value that worked for both situations (and that didn't break various macro hygiene rules), so I've added the above trait bound. If there's a way to implement the conversion as part of the macro, then implementing From/Into
would no longer be necessary.
src/guest_memory.rs
Outdated
} | ||
} | ||
|
||
impl Into<u64> for GuestAddress { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure
Hi, I rebased & pushed two changes: using integer arithmetic + cast in I also tried to use some trait + bounds constructs to have Rust "know" which alignments are ok to cast into which. Although it works, things get a bit too convoluted so I've kept this simple approach where both |
The "integer-atomics" feature can be replaced by using intrinsic `cfg` annotations (such as detecting different platforms). Also removed the implementation of `AtomicValued` for `AtomicBool` because we cannot safely reinterpret locations in guest memory as `bool` (values other than 0 and 1 may lead to undefined behaviour). Renamed `AtomicValued` to `AtomicInteger`, which seems like a more accurate description. Signed-off-by: Alexandru Agache <aagch@amazon.com>
Signed-off-by: Alexandru Agache <aagch@amazon.com>
Hi, I've added unit tests as well in a new commit, and removed [RFC] from the PR name. Looking forward to any (re) reviews :D |
Signed-off-by: Alexandru Agache <aagch@amazon.com>
Signed-off-by: Alexandru Agache <aagch@amazon.com>
assert_eq!( | ||
a.offset::<u64>(u32_offset).unwrap_err(), | ||
AlignmentError::Misaligned | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about adding another case:
c = a.offset::<u64>(u32_offset).unwrap();
assert_eq!(c.addr, b.addr.unchecked_add(u32_offset));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added!
@@ -193,6 +267,90 @@ pub trait Bytes<A> { | |||
/// Part of the data may have been copied nevertheless. | |||
fn read_slice(&self, buf: &mut [u8], addr: A) -> Result<(), Self::E>; | |||
|
|||
/// Returns a reference that allows atomic operations for `T` at the specified address. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm hesitating whether the atomic related interfaces should be hosted by a dedicated trait.
Per my understanding, we have three types of memory access modes,
- read/write in byte stream mode.
- read/write in naturally aligned mode (word/double word/quad word).
- read/write/swap/compare_and_exchange in naturally aligned atomic mode with memory ordering.
Bytes::{read/write/read_slice/write_slice/read_from/write_to/read_exact_from/write_all_to} should access memory in byte stream mode.
But Bytes::{read_obj/write_obj} should access memory in naturally aligned mode.
So should we move read_obj/write_obj/atomic_ref/write_atomic/read_atomic out of the Bytes trait?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think both the name and some of the doc comments (i.e. the one about Candidates which may implement this trait include:...) are a bit misleading for Bytes
(in my mind I've always used something along the lines of RandomAccessMemory
). Your overview of access types sounds reasonable, and what's forcing us right now to explicitly turn operations like read_obj
into memcpy
s is cross region access (I'm working on a proposal to address that, and will post something later this week).
To answer your question, I'm not totally sure what the best place for these operations is in the long term; having them in Bytes
seems like the best option for now. I think we still need a bit more time to figure out how the stable vm-memory
interface looks like, and we should address some of the existing concerns first.
Signed-off-by: Alexandru Agache <aagch@amazon.com>
Signed-off-by: Alexandru Agache <aagch@amazon.com>
4ece6b8
to
beb7ab6
Compare
Closing for now in favor of #114. Thanks for the discussion so far everyone! |
Related to #102
Hi,
This PR adds four new methods (
read_atomic
,write_atomic
,read_aligned
, andwrite_aligned
) to theGuestMemory
andGuestMemoryRegion
interfaces. Here are some details:The proposal is mainly about adding
read_atomic
andwrite_atomic
as part of theGuestMemory
interface in order to provide a more straightforward (and always available) mechanism for performing atomic accesses with explicit semantics.The
Aligned<Addr, T>
abstraction appears interesting and useful to represent additional alignment information and/or requirements about an address. It needs some more methods to improve the ergonomics of casting + offsetting, together with examples, but for the time being I am really curious if others also think this is worth pursuing.For now, the new methods are added directly to
GuestMemory
andGuestMemoryRegion
(as opposed to being part of theBytes
interface) becauseBytes
provides no guarantees about the alignment of the container (whereas bothGuestMemory
andGuestMemoryRegion
are expected to be aligned to page boundaries -- maybe we should mention this explicitly btw). There are a couple of ways to reconcile the interfaces going forward, but I think what matters for now is to have a well-defined and straightforward way of performing atomic accesses based on a genericM: GuestMemory
object.I've also ran a couple of synthetic tests, and the improvement for atomic reads/writes seems quite promising (i.e. > 2x). I'll try to do some more after I manage to get a better setup going.