Skip to content
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 fences to mutex methods #33

Closed
wants to merge 2 commits into from

Conversation

thalesfragoso
Copy link
Member

Without the fences the Mutex doesn't behave as expected.

Given this example:

static FOO: Mutex<RefCell<bool>> = Mutex::new(RefCell::new(false));

pub fn main() {
    let mut count = 0;
    loop {
        let cs = unsafe { CriticalSection::new() };
        if *FOO.borrow(cs).borrow() {
            break
        }
        count += 1;
    }
    let mut black_box = 0;
    unsafe { ptr::write_volatile(&mut black_box as *mut _, count); }
}

#[no_mangle]
pub extern "Rust" fn bar() {
    let cs = unsafe { CriticalSection::new() };
    *FOO.borrow(cs).borrow_mut() = true;
}

It's expected that the value of FOO will be evaluated at each loop pass, right now that doesn't happen, FOO is only evaluated once and if the value is false then the function branchs to an infinite loop:

.LBB1_2:
        b       .LBB1_2

godbolt link (Remove the comment to add the fence and see the difference)

I decided to go for a fence instead of a compiler_fence to cover the cases where the processors have a cache or are out of order. We could change SeqCst for a pair of Acquire and Release, but then I think we would have to put one of them in the Drop implementation of the CriticalSection, but we can't really rely that drop will run (mem::forget), so I went with SeqCst, maybe I'm missing some other way...

It seems that we would need to yank previous versions, unfortunately.

@cr1901
Copy link

cr1901 commented May 24, 2020

@thalesfragoso Note that fences at present do not work on msp430. And I am not certain how to fix this as I don't handle the LLVM side of things for msp430 :/.

@Disasm
Copy link
Member

Disasm commented May 24, 2020

@cr1901 a compiler fence could be implemented/used for MSP430, it's just a type 0 of LLVM AtomicFence that lowers to nothing for x86 and ARM.

@andre-richter
Copy link
Member

How are core library mutexes doing it wrt to acquire/release?

Mutexguards should have a similar problem, ain’t they?

src/lib.rs Outdated
@@ -61,6 +62,7 @@ impl<T> Mutex<T> {
/// This does not require locking or a critical section since it takes `&mut self`, which
/// guarantees unique ownership already.
pub fn get_mut(&mut self) -> &mut T {
fence(Ordering::SeqCst);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this one needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user has a static mut and knows that the program won't get interrupted at some specific point, and then, they decide to call get_mut instead of lock. In that case, if we don't have a fence the program will get miscompiled. But right now I'm not 100% sure that this use case isn't UB by itself...
godbolt link

@thalesfragoso
Copy link
Member Author

How are core library mutexes doing it wrt to acquire/release?

Mutexguards should have a similar problem, ain’t they?

I think that in the case of the std::sync::MutexGuard they can rely on drop because if the guard doesn't get dropped the mutex will remain locked forever. That might also make sense for us, and I was going to try that today, but I realized that CriticalSection is Copy and because of that we can't implement Drop for it.

@thalesfragoso thalesfragoso marked this pull request as draft May 24, 2020 18:13
@thalesfragoso thalesfragoso marked this pull request as ready for review May 24, 2020 18:34
@thalesfragoso
Copy link
Member Author

As discussed in the matrix chat, it seems that fence (instead of compiler_fence) is only needed when dealing with a multi master bus, which it's out of scope for this crate, so I changed it to use just a compiler_fence.

I also provided a solution for the msp430 case, but the one I found will only compile on nightly, but that seems to be the case in the msp430 crate too, so it seems okay. CC @cr1901

CC @adamgreig

@cr1901
Copy link

cr1901 commented May 24, 2020

@thalesfragoso I don't have a problem making bare_metal nightly on msp430; we can fix that later. The msp430 crate is indeed nightly for now and will likely be so until asm! stabilizes.

I want a second opinion though: cc: @pftbest and @awygle.

@adamgreig
Copy link
Member

Could we just document that CriticalSection::new requires the caller ensure a compiler fence is inserted and not otherwise change the body? That way cortex-m and msp430 will continue to work (since both take actions that result in fences being inserted when creating a CS), and no new code is added here.

That doesn't resolve the get_mut() issue which I am not yet convinced we actually need to change but I'm certainly open to being wrong there. I'm also not sure if riscv effectively inserts a fence when disabling interrupts, as I don't really understand how its register modification works.

@pftbest
Copy link

pftbest commented May 25, 2020

I also agree with @adamgreig, the fences should be added only when CriticalSection is created, not on every borrow. For example, you obtained a critical section and then do 3 borrows in a row:

    let cs = ...
    let a = *FOO.borrow(cs).borrow();
    let b = *BAR.borrow(cs).borrow();
    let c = *FOO.borrow(cs).borrow();

You don't need a fence inserted in between each borrow here, because nothing else can change the values of FOO and BAR while you are holding a critical section token. Adding fences here would be a pessimisation, because compiler will no longer be able to see that a and c is the same exact value and optimize out second access to FOO.

@thalesfragoso
Copy link
Member Author

I also agree with @adamgreig, the fences should be added only when CriticalSection is created, not on every borrow. For example, you obtained a critical section and then do 3 borrows in a row:

That's already the case.

@pftbest
Copy link

pftbest commented May 26, 2020

That's already the case.

I missed that, sorry.

@adamgreig
Copy link
Member

To summarise discussion in today's meeting on Matrix: we require a fence at the end of the critical section as well, because otherwise the compiler could re-order a store to after the CS finishes, which could race with another thread. Since the CS might be safely forgotten, there's no way to ensure this inside the CS type. Instead, we should add a safety precondition to the CS documentation to say that users (the architecture crates such as cortex-m, cortex-a, msp430, riscv, etc) must ensure a compiler fence is inserted immediately after entering and before leaving the CS.

Currently this is mostly the case, for example cortex-m uses either an FFI call or inline asm with a memory clobber to enable and disable interrupts, which has the effect of ensuring a suitable fence. We should make these fences explicit (although it's a shame that compiler_fence has a potential panic in debug builds).

So, the update to bare_metal can probably just be a safety documentation on CriticalSection, but the architecture crates will all need updating to ensure suitable fences are used.

I don't think this resolves the Mutex::get_mut() concern yet though. I'm not sure how that should best be resolved. Since it requires &mut self, there shouldn't be any way in safe Rust to trigger this problem anyway...

@adamgreig
Copy link
Member

adamgreig commented May 26, 2020

For a little historical context: rust-embedded/cortex-m@a396a68 and rust-embedded/cortex-m@93c901d

@thalesfragoso
Copy link
Member Author

As it was discussed in the meeting, it seems that the best approach to this problem is to add one more condition to the CriticalSection::new method and rely on the arch crates to implement it correctly. Moreover, a closure approach seems to deal with this better.

I'm closing this in favor of this new solution, bare-metal should only need to update its documentation.

bors bot added a commit that referenced this pull request Jun 9, 2020
34: Document suitable conditions for the safety of CriticalSection and Mutex use r=adamgreig a=thalesfragoso

For more information, see #33 and more specifically, #33 (comment).

I'm a bit on the fence (heh) with respect to the documentation of `get_mut`, I would like some feedback on that.

Co-authored-by: Thales Fragoso <thales.fragosoz@gmail.com>
Co-authored-by: Thales <46510852+thalesfragoso@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants