Skip to content

[BEAM] Unsynchronized access to a shared static mut variable #113

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

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions active_discussion/embedded/static-unsynchronized.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Unsynchronized access to a shared `static mut` variable

Sometimes interrupt handlers need to share state. This is done using a static
variable. We claim that *no* synchronization is needed to access the shared
static variable if the interrupt handlers run at the same priority.

Note that:

- The programs in this document target the [Basic Embedded Abstract Machine
(BEAM)][beam]. Please become familiar with the linked specification before you
read the rest of this document.

[beam]: https://github.com/rust-lang/unsafe-code-guidelines/pull/111

- In these programs we assume that [rust-lang/rfcs#2585][rfc2585] has been
accepted and implemented.

[rfc2585]: https://github.com/rust-lang/rfcs/pull/2585

## Example program

Consider this program

``` rust
#![no_std]

#[no_mangle]
unsafe fn main() -> ! {
unsafe {
asm!("ENABLE_INTERRUPTS" : : : : "volatile");
}

loop {
// .. any safe code ..
}
}

static mut X: u128 = 0;

#[no_mangle]
unsafe fn INTERRUPT0() {
let x: &mut u128 = unsafe { &mut X };

// .. any safe code ..
}

#[no_mangle]
unsafe fn INTERRUPT1() {
let x: &mut u128 = unsafe { &mut X };

// .. any safe code ..
}
```

Note that "any safe code" can *not* call `main`, `INTERRUPT0` or `INTERRUPT1`
(because they are `unsafe` functions), use `asm!` or access registers.

**Claim**: this program is well-defined / sound. Rust aliasing rules are
preserved because no preemption is possible between `INTERRUPT0` and
`INTERRUPT1`.

In fact, I would say that this program (ignoring `main`) is equivalent to this
code, which is sound (and reminds me of cooperative scheduling):

``` rust
let mut X: u128 = 0;

loop {
sleep(random());

if random() {
INTERRUPT0(&mut X);
} else if random() {
INTERRUPT1(&mut X);
}
}
```

## Questions

- Can this program be misoptimized given that the compiler has *no* information
about `INTERRUPT0` and `INTERRUPT1` executing "cooperatively"?
Copy link
Member

@RalfJung RalfJung May 5, 2019

Choose a reason for hiding this comment

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

The crucial aliasing point here is: the moment INTERRUPT1 starts executing, any previously created pointer such as everything done in INTERRUPT0 gets invalidated.

So, safety relies on the assumption that the safe code cannot somehow leak the &'static mut that it got -- for example, through a static X: RefCell<Option<&'static mut u128>> or so. I assume that is possible for safe code even on BEAM, so this would not be a safe abstraction. But if you change it as follows, I think this hole is plugged:

#[no_mangle]
unsafe fn INTERRUPT1() {
    let x: &mut u128 = unsafe { &mut X };
	inner(x);

	// Crucially, inner is generic in the lifetime and hence
    // cannot leak the reference.
    fn inner(x: &mut u128) {
      // .. any safe code ..
    }
}

Besides this point, I agree that the program is fine as far as Stacked Borrows is concerned. I can only hope LLVM agrees with that. ;) The compiler cannot know that the functions cooperate, but when it doubt it has to assume that they do.