-
Notifications
You must be signed in to change notification settings - Fork 19
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
Owned VolatileCell
type?
#31
Comments
I started to prototype a possible implementation in https://github.com/rust-osdev/volatile/blob/volatile-cell/src/cell.rs (based on #29). It looks like there is indeed no
In comparison, I built with Rust 1.61, which does not contain rust-lang/rust#98017. We see that the
So this looks promising. However, I'm not sure if this is something that we can rely on. Is this behavior guaranteed by the language now? The above result was acquired using |
Unfortunately, the Alternatively, we might be able to exploit rust-lang/rust#106180 once it's merged. As I understand it, adding a |
I kinda fundamentally don't get what you're going for here. The whole point of MMIO being separate types is that it's not normal memory. And one of the things about it not being normal memory is that you don't ever actually have exclusive ownership of the memory. In addition to however the rust code decides to divide up the task of accessing that address, there's also the hardware that's always also at least looking at that address, and possibly modifying what's at that address. You are permanently sharing an address with an additional party, since before your program begins. |
It is my understanding that |
The main motivation for an "owned" type like this that you can abstract the MMIO memory using normal
I'm awary of that. The |
@repnop Thanks a lot for the info!
That's what I feared.. There is no way to restrict an inner field to a shared reference, right? I mean something like
Thanks, I was not aware of that Zulip thread. From the thread:
I assume this refers to this comment above?
I'm now sure if I understand this correctly, but I assume that the issue is the |
Not sure if this is something, but just wanted to mention the idea: |
Is the only problem with |
I just don't think Rust as it is today can handle this. Until there's an actual language level change I wouldn't ever suggest anyone try to do that. I realize that this is probably "easy" for me to say because I only program for a single embedded device and not a whole ecosystem of stuff, but for the GBA I never use volatile with types that can't be read/written in a single instruction (1, 2, or 4 bytes). It's just ints or newtyped ints all the way through. If you absolutely need it to look struct-like based on the usual struct definition syntax, I'd suggest a proc-macro that accepts a C struct as the input but expands to a newtype over usize (holding the base address) and with methods for the address of each "field" of the region (each method offsets by a const that the proc-macro has computed). If you want a VolatileCell wrapper that would need an RFC so it can get proper compiler support. |
Probably, but I still would like to understand what's the actual issue. Is there a potential codegen issue that could still lead to a
I fully agree that a pointer-based approach is the better and safer solution for now. However, the cell-like approach is still quite popular in the ecosystem, e.g. through the |
This would basically be the pointer-based approach, right? We could not create a struct with e.g. three volatile
I guess it could still be an issue if values change between (spurious) reads and LLVM does not expect that? |
We could use a macro to create another struct. The struct would have fields with the same names as the original, except all types would be switched out for zero sized types. That way the whole struct would still be zero sized, but I'd still be possible to access the fields like normal fields. |
Ah, but then we would need to store the field offsets somewhere. So we could get something similar to @Lokathor's proposal above:
|
The offset could be part of the zero sized type for each field e.g. with const generics. I'm currently drafting something up, will publish later today.
Using methods has the limitation that splitting a reference to struct into references to fields is not possible. |
This worked surprisingly well: https://github.com/Freax13/zst-volatile |
One problem, among possibly several, is that people seem to want to be able to write code like I'm probably not against some sort of VolatileCell type, but I also don't wanna have to do an entire RFC and impl cycle because there would be a number of difficult bikesheds to paint I'm sure. That's why I personally just gave up on the structure thing and went with what Rust of today does support well. I'm definitely happy to keep answering questions though. |
Speaking as someone who needs a cell-like Forgive me if this has been answered, but were there any soundness or other problem(s) identified with the old version |
As I understand it, the general issue is that we create references to the volatile value, which permits the compiler to insert spurious reads. For example, the |
I think this has already been said, but the answer is no. References are still assumed to point to "normal" memory where reads do not have side-effects. The LLVM To support something like VolatileCell, one of two things needs to happen:
|
FWIW I think this would be a mistake. Just compare this with atomics: assigning to an atomic variable is very different from assigning to a local variable; atomic variable assignment is more like sending a message to everyone else who has access to this location. A lot of the issues around data races and the complexities when writing concurrent shared-memory code arise because with shared memory, "sending a message" and "mutating some local state" look exactly the same. That's why message passing simplifies things so much: now "sending a message" is very different from "mutating local state". But of course Rust shows that you don't need message passing to tame concurrency, you can actually stay in the shared memory idiom and still have all the benefits of message passing concurrency (explicit points of communication) with a sufficiently strong type system that guarantees that simple assignments are never "sending a message". Volatile writes, of course, are sending a message to some other device, so making them look like regular assignments would IMO be a big loss, and lose out on the "fearless concurrency" achievement of Rust. |
Perhaps I should clarify: I meant to emphasize that what I've most often seen is that people want the field projection. So the write could be done with a method instead ( |
Blocked on rust-lang/unsafe-code-guidelines#411 |
I finally had some time to take a closer look at @Freax13's proposal at https://github.com/Freax13/zst-volatile. I think it looks quite promising:
The only potential issue I'm seeing is that it stores the base offset inside the reference to the ZST, which might violate pointer provenance. It effectively performs the following operation: fn main() {
let mut x = 5;
let ptr = &mut x as *mut i32;
let zst_ptr: *mut Pointer = ptr as usize as *mut Pointer;
let zst_ref: &mut Pointer = unsafe { &mut *zst_ptr };
unsafe {zst_ref.write(10)};
dbg!(x);
}
struct Pointer;
impl Pointer {
unsafe fn write(&mut self, v: i32) {
let ptr = self as *mut Pointer as usize as *mut i32;
unsafe { *ptr = v; }
}
} When run under miri, it throws two "warning: integer-to-pointer cast" instances, but is happy otherwise. Given that strict pointer provenance rules are only a proposal right now, I see no reason why this approach should be unsound with current Rust versions. Or am I missing something? If the approach is sound, it might be a good idea to integrate it into the |
(I also tried to remove the two
It looks like zero-size retags are handled in a special way. I'm not familiar enough with miri to assess whether this is a violation of the stacked borrow model or just a limitation of the current implementation.) |
The problem with casting a reference to a pointer directly is that under stacked borrows the provenance of new pointer is the same the old reference had. In particular the spatial component of the provenance stays the same, so the new pointer can only access bytes that were already accessible by the reference it was created from. Any pointer created by casting from a ZST reference can access exactly 0 bytes. Casting to an integer avoids this because it implicitly "exposes" the pointer's provenance when first casting the (non-ZST) pointer to an integer and picks the "exposed" provenance back up when casting back from an integer to a pointer. This is equivalent to calling |
Thanks for the explanation!
If it stays the same, the
Agreed! |
Almost. IIUC At least that's my understanding, but I am by no means an expert, so feel free to double check everything I'm saying and correct me if I'm wrong. |
What @Freax13 says sounds right. Note that with Tree Borrows the rules are a bit different and the non-usize version of this is actually allowed, but there is no final decision yet on whether this should be allowed for not. The usize version basically means that the provenance of the reference is irrelevant and uses |
Some people expressed interest in a volatile
Cell
-like type that owns the value, similar to thev0.3
interface of this crate. I think that such a type would be a nice addition to the pointer types proposed in #29, but the question is whether it is possible to implement such a type in a sound way.The fundamental issue is that references are marked as
dereferenceable
by the compiler, which permits LLVM to insert spurious reads. This is not valid for volatile values since they might change in between reads. This issue was e.g. discussed in this thread on the Rust internals forum in 2019, with the conclusion that such a type would require special language support.About half a year ago, rust-lang/rust#98017 was merged, which (if I understand it correctly) removed the
dereferenceable
annotation from&T
whereT
contains anUnsafeCell
. Maybe this change makes it possible to safely implement aVolatileCell
with an innerUnsafeCell
now?The text was updated successfully, but these errors were encountered: