How to best handle updates to rust structures memory from external programs

Hi,
well in my embedded Rust program I'm having a specifc structure with a fixed size. It's memory location is passed to another "program" or better other hardware (the GPU) to be processed. While processing the other "process" (the GPU) will update the data that is pointed to. However, Rust does not know that this is happening under the hood and may optimize stuff away as it assumes the data keeps unchanged.

My current approach feels a bit "ugly" to me:

fn send_message<'a, T: Message>(mut message: T) -> Result<&'a T, ()> {
  let ptr: *mut T = &mut message;
  send_to_gpu(ptr);
  // leaving out the code waiting for the GPU to respond
  let result = unsafe { core::mem::transmute::<*mut T, &mut T>(msg_ptr) };
  Ok(result);

Its, from my point of view crystal clear that I'm always dealing with the same type T here, so I'd like to find a "safer" way of letting rust now that the data has been changed should "reconstruct" the type T from it's pointer. Is there a way without using the last resort function transmute ?

Thanks in advance for any hints...

I mean, if msg_ptr has type *mut T, you can just do &mut *msg_ptr instead of the transmute.

However it sounds like you believe the Rust compiler thinks the data was unchanged, if this is the case, read_volatile sounds relevant. However I'm not too familiar with the exact details of this.

Hey, that's exactly the case. It's proven that the Rust compiler thinks the data was unchanged ... In theory I could just take the input parameter message and return the same, knowing the data may have changed, but the optimizer is really stripping this away as he has no clue about the stuff happening behind the scenes...... I'm not sure how much read_volatile might help here but I could give it a try :slight_smile:

I'd really like to tell the compiler: "beware the data of the struct might have been changed without your knowledge, treat is as such! May be wrapping all in an unsafe block would do this trick?

Best regards..

Perhaps you could post some more code? In the current snippet, the msg_ptr seems to have appeared out of thin air, so I can't quite tell what happened. The compiler has a rule that during the lifetime of a mutable reference, no other references to that item may exist, so you might be running into this. Since raw pointers do not have this uniqueness guarantee, changing to those may help.

An unsafe block doesn't turn off the borrow checker! All it does is allow you to do a few extra things such as dereferencing raw pointers and calling unsafe functions.

Well, fair point... giving more context might help...
Here we go :wink:

// this function takes/consumes [message] and passes the pointer to this message to
// the GPU and waits until a specific mailbox "flag" in memory is set, indicating the GPU has
// finished processing. If this is done the GPU has updated the memory location of [message] at
// runtime. neither the compiler not the borrow checker knows about this memory change..
pub(crate) fn send_message<T: MailboxMessage>(channel: MailboxChannel,  mut message: T) -> MailboxResult<T> {
  // here I'm using the address of [message] to be passed to the GPU's mailbox
  let msg_ptr: *mut T = &mut message;
  write_gpu(msg_ptr as u32);
  wait_gpu_done();
  // once the GPU has finished processing I'd like to return the message with it's updated content
  // a naive approach would be:
  if message.state() == Message::ResponseOk {
    Ok(message)
  } else {
    Err("Error in Mailbox call")
  }
  // doing so would just optimize the whole function to do nothing as the compiler is quite right in
  // assuming then [message] has never beeing changed here by any line of code...
  // but instead it has been changed by the GPU. So we really leaving the safety zone and contracts of rust
  // however, I'd like to let Rust(the compiler) know that this assumption is wrong and [message] has 
  // been changed...
  // I tried this:
  let result = unsafe { core::ptr::read_volatile(msg_ptr as *const T) };
  if result.state() == Message::ResponseOk {
    Ok(result)
  } else {
    Err("Error in Mailbox call")
  }
  // this also seems to get optimized away as rust again might assume the value could never have changed ?
}

fn test() {
  // this function creates a new structure that implements [MailboxMessage]
  let mut tag = propertytags::ClockrateGet::new(clock_id as u32, 0x0);
  send_message(MailboxChannel::PropertyTagsVc, tag)
}

The whole behavior only changes if I do a println!("return state {}", result.state()) as this seem to force the compiler to keep the message/result around as it need to be accessed for output.

I do hope this gives some more insights and enables you to further suggest what might help here?

Thanks in advance for your time.

I am worried by the cast to u32, because I feel that if you passed the pointer to ffi code without having to cast it, it should correctly notice that the pointer went somewhere it doesn't understand.

Well, good catch. It's a bit more "worse" than that :open_mouth:...
It's not even ffi code that got's called. I also need to "or" the address with 0xC000_0000. This u32 value is then written to a specific memory address (on a Raspberry Pi) where it gets picked up by the GPU firmware and will respond to it. So Rust code only sees the address value beeing written to an "arbitrary" memory address....
And then after some "magic" (from Rust point of view) I try to read a different value from this memory location, where the compiler might argue: "well, it's still the same value, no need to actually read, what could have changed the value???)

So to be really honest this goes to the GPU:

let msg_ptr: *const T = &message;
let msg_ptr_uncached: u32 = (msg_ptr as u32) | 0xC000_0000;
write_gpu(msg_ptr_uncached);

Might be the compiler is therefore loosing track of the pointer stuff?
The write_gpu function is written in rust by the way and as said just putting the value to an arbitrary memory location. (The *.Register thing in the code below is a MMIO register, so represents a memory location)

fn write(channel: MailboxChannel, data: u32) -> MailboxResult<()> {
    while (MAILBOX1_STATUS::Register.get() & MAILBOX_FULL) != 0x0 {}
    let value = (data & 0xFFFF_FFF0) | ((channel as u8) & 0xF) as u32;
    MAILBOX1_WRITE::Register.set(value);
    Ok(())
}

Using a const pointer sounds like a very bad idea if you want the compiler to expect it to change.

That said, perhaps you could cast the integer back into a pointer? If the two upper bits in 0xC000_0000 are never set in your pointer, perhaps you could do something like:

let response_ptr = (msg_ptr_uncached ^ 0xC000_0000) as *mut T;
let response = ptr::read_volatile(response_ptr);

You need to watch out if T has a destructor.

Well, that's true, but I tried also all sorts of mut. Moving a mut message, borrowing a mut reference etc... the result is always the same. Unfortunately recasting the pointer with ^ 0xC000_0000 did not helped...
The only "workaround" that seem to work reproducible is either using println! on the result pointer address or using `#[inline(never)] on the fn send_message. This seem to do the trick on the compiler but does not feel very stable...

Best Regards...

I'm impressed that the compiler can compile it away even when reading from ^ 0xC000_0000 as that would only give you the same pointer back if the original stack pointer had those two bits as zero. I guess the compiler has some knowledge of where stack pointers lie...?

I'm sorry but if none of this works, I don't have a good solution. Maybe someone else has more experience with embedded devices.

Hey,
nevermind. Thanks a ton! for your time and help so far. It shed some light already and gives me clues where to improve my code.

Thx again!

1 Like

little “upvote”...
Is there anyone having an idea how to give the rust compiler a hint that the data structure has been changed from an external “process” so it does not optimize things away assuming the data could not have changed at all?

Thx in advance...

Is this data structure a hardware register? a memory shared with a GPU or FPGA? Are you using no-std? Google "rust volatile" and start from there.

Hi,
yes, I'm using no_std on embedded/Raspberry PI. The data is is at a memory address that is shared with the GPU. I've tried several attempts with ptr::read_volatile and things, but still the compiler seems to optimize things away, assuming nothing is changed at this memory address.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.