-
Notifications
You must be signed in to change notification settings - Fork 57
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
Can we have VolatileCell #411
Comments
I think the question whether we "can" have is fairly straight-forward -- yes, we can totally do that. I think it wouldn't be too hard either, operationally speaking. For instance we could say that For |
Is this something that is actually needed? The main use case is making "register layout structs" in Peripheral Access Crates (PACs): struct MyRegisterBlock {
foo: VolatileCell<u32>,
bar: VolatileCell<u32>,
baz: VolatileCell<u32>,
}
struct VolatileCell<T: Copy>(UnsafeCell<T>);
impl<T> VolatileCell<T> {
fn read(&self) -> T { self.0.get().read_volatile() }
fn write(&self, val: T) { self.0.get().write_volatile(val) }
}
let regs: &MyRegisterBlock = &*(0x4000_0000 as *const MyRegisterBlock);
regs.foo.write(0x42); However, you can accomplish the same thing by staying entirely within raw pointers: struct MyRegisterBlock(*mut u32);
impl MyRegisterBlock {
fn foo(&self) -> Reg<u32> { Reg(self.0.add(0)) }
fn bar(&self) -> Reg<u32> { Reg(self.0.add(1)) }
fn baz(&self) -> Reg<u32> { Reg(self.0.add(2)) }
}
struct Reg<T: Copy>(*mut T);
impl<T> Reg<T> {
fn read(&self) -> T { self.0.read_volatile() }
fn write(&self, val: T) { self.0.write_volatile(val) }
}
let regs: MyRegisterBlock = MyRegisterBlock(0x4000_0000 as _);
regs.foo().write(0x42); Pros and cons:
So overall, I don't think there's any reason to prefer the struct approach. in Embassy we've been using PACs generated by chiptool, which generates code using raw pointers, out of concerns for the So IMO it's not worth it to add exceptions to the memory model to support usages like |
I'm an outsider here and totally happy with not having VolatileCell, if the domain experts say that the status quo is totally sufficient. @Lokathor has opinions on this topic, IIRC. |
I specifically would like |
My opinions are:
@Dirbaio I think what people "really want" is something that also has magical field projection so that you can do code like this: #[repr(C)]
pub struct Controls {
display_control: u16,
display_status: u16,
vcount: u16,
}
pub const MMIO: &VolatileCell<Controls> = whatever_expression_here!(0x0400_0000);
fn main() {
// magical field projection from `&VolatileCell<Controls>` into `&VolatileCell<u16>`
let display: &VolatileCell<u16> = &MMIO.display_control;
// call some method to assign some value
display.write(1);
// or we can chain the expression
let y: u16 = MMIO.vcount.read();
} I think without some sort of field projection thing added to the language it's less compelling, though possibly still useful even then. Also note that the above example doesn't bother with when things are unsafe or not, which things are readable or writable, etc. There's a lot of design that can go into an mmio abstraction type. @chorman0773 can you say more about that linker stuff? I think you told me once but I've forgotten if you did, and more details about that might make a more compelling case for needing |
Yes. In SNES-Dev, I use linker scripts to define access to the MMIO registers. For stuff that is specific to SNES-Dev, the address is even actualy known at compile time, as it is generally assigned by the linker itself, and given a special type in the mapping table. |
That part sounds similar to GBA dev, but with GBA it's generally done with just |
@Dirbaio in your example, MyRegisterBlock is no longer a ZST. Can that approach work without holding on to the pointer? |
Thanks for the mention! As far as I know, volatile cells are still commonly used in the Rust embedded an OS ecosystems. For example:
Most of these projects are aware that this approach is not sound, but still haven't changed their code. I assume that it's a mix of personal preference (structs are much easier to write than doing manual pointer arithmetic) and migration cost (requires breaking changes across the entire ecosystem). So I think that there is definitely demand for a sound |
I agree that it's less of an issue when the pointer arithmetic is generated by tools. However, I think most people write things by hand first and then create tools to autogenerate the boilerplate code later using the same design. So the design that is easier to write by hand is often used for code generation too. For example, I think |
That is not compatible with CHERI, right? For CHERI you did at least have to store a raw pointer to preserve the full capability. |
in the struct approach, you use a HAL wouldn't store a
from the Embedded WG's perspective, we were definitely aware of this and studying possible solutions (it's one of the reasons chiptool exists for example). The push for it has essentially stopped once rust-lang/rust#98017 was merged, which meant VolatileCell is no longer unsound in the current implementation. It was not an "okay, now let's push to make VolatileCell sound in the memory model" decision, it was more like "okay, it's less urgent now, and we don't have much manpower so let's leave it for now". Moving to pointers is still something I'd personally like to see done, due to the other advantages I mention in this thread. (this is my personal opinion though, not the WG's)
my experience is the contrary, everyone starts adding support for a new chip by grabbing a I disagree reg structs are easier to write by hand, too. Documentation always says "register FOO is at offset 0x4c". Translating that to pointer math is trivial, you just add 0x4c. Translating that to a struct you find yourself counting registers manually to infer at which offset each field is, and manually calculating sizes of dummy padding hole arrays. I think people write registers structs simply because that's how it's always been done in C. There's a good reason to use structs in C: you can use fields with |
You may be right. I've never seen a hardware manual for any CHERI device. |
That is concernying from a t-opsem perspective, this is certainly not the outcome we hoped for when resolving the Anyway, from my perspective -- what's missing here is a VolatileCell RFC. This doesn't seem like a gap in the language that needs solving to gain some crucial expressiveness, so I don't have writing such an RFC anywhere on my own roadmap. t-opsem would be involved only insofar as we'd make the memory model actually support the desired semantics, but until such an RFC is accepted, I think this is S-status-not-opsem. |
For the SNES-Dev specific parts, they don't have a fixed address and just live where the mapping table puts them. BEing able to float that arround the rest of the cartridge mapped memory allows for more compact programs and more effective bank-sensitive relaxations. And having the SNES stuff also be defined in the linker, depsite having fixed addresses, is good for consistency.
I'd say there were a lot of consequences from removing dereferenceable from |
A Our use case is writing MMIO drivers based on manually written structs. We are currently in an unsound situation in our codebase, anticipating helpers to make volatile accesses easier before rewriting everything with raw pointers. It would be a blessing if the language allowed to write I would be genuinely interested in driving this forward and perhaps writing an RFC. The right way to start drafting would be the |
Field projections would also be useful for |
latest I am aware of is rust-lang/rfcs#3318 |
Field projections is orthogonal to the issue at hand though? The question is "do we add some special case to opsem to guarantee no speculative reads on |
The answer isn't just a yes or no, it's actually "we don't right now, but we could do that, but it's non-zero work to do, and the work won't be of much value without also having field projection, so we should evaluate the cost/benefits with all that in mind" |
As Ralf said, the opsem side of things is pretty cut-and-dried. If lang approves a |
ah okay! I see the plan now, thanks for clarifying. Just to confirm:
|
I think so, yes.
As an pub struct VolatileMem<T> {
ptr: NonNull<T>,
}
impl<T: Copy> VolatileMem<T> {
pub fn get(&self) -> T;
pub fn put(&mut self, val: T);
pub fn map<F: Field<Base = T>>(self) -> VolatileMem<F::Type>;
}
|
Looks like we should have a thread for VolatileCell API design somewhere -- this thread here was intended for the opsem questions around it, which are quite orthogonal. (Or we declare the opsem parts resolved and re-purpose this here about API design -- including whether that design even needs a |
Has anyone considered using My understanding is that Maybe a new type is needed to say "this is not actually dereferenceable" (in addition to (I'm sorry if this has already been answered or if that's not the right place to ask) edit: seems that Redox is using |
|
This question is intended to replace #33 and #265, and ask the general question of whether or not we can have a type, either user-defined or language-provided, that, when wrapped in a shared reference, guarantees that no accesses are introduced at the operational semantics level that are not part of the original program (and thus, if volatile accesses only are used, an implementation won't introduce any speculative reads).
If such a type can exist, the second question is what is the operational semantics of retagging as a shared reference to such a type.
The text was updated successfully, but these errors were encountered: