-
Notifications
You must be signed in to change notification settings - Fork 61
[BEAM] &'static mut
references to static variables and LLVM noalias
#115
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
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
# `&'static mut` references to static variables and LLVM `noalias` | ||
|
||
Some programs need to move values between interrupt handlers (or between `main` | ||
and an interrupt handler); this operation has to be done using static variables. | ||
Performing this send operation "by value" can result in expensive `memcpy`s | ||
(from handler A to the static variable and then from the variable to handler B). | ||
A way to reduce the runtime cost is indirection: instead of memcpy-ing the value | ||
(`T`) one can send (memcpy) a pointer that owns the value, like a `Box<T>`. | ||
|
||
Sometimes heap allocators are avoided in embedded code for performance or | ||
reliability reasons (e.g. a potential OOM condition is undesirable). In this | ||
scenario `Box`, `Rc`, `Arc` and other pointers are not available to the | ||
programmer. This leaves `&'static mut` references as the only non-`unsafe` | ||
choice for indirection -- the `'static` lifetime is required because the | ||
reference will be (temporarily) stored in a static variable. | ||
|
||
We have a pattern to create a `&'static mut` reference from a `static mut` | ||
variable, but is it sound? | ||
|
||
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 | ||
|
||
## Creating a `&'static mut` references | ||
|
||
Consider this program: | ||
|
||
``` rust | ||
#![no_std] | ||
|
||
#[no_mangle] | ||
unsafe fn main() -> ! { | ||
unsafe { | ||
asm!("ENABLE_INTERRUPTS" : : : : "volatile"); | ||
} | ||
|
||
let x: &'static mut Type = unsafe { | ||
static mut X: Type = Type::new(); | ||
|
||
&mut X | ||
}; | ||
|
||
loop { | ||
// .. any safe code .. | ||
} | ||
} | ||
|
||
#[no_mangle] | ||
unsafe fn INTERRUPT0() { | ||
// .. any safe code .. | ||
} | ||
``` | ||
|
||
Note that "any safe code" can *not* call `main` or `INTERRUPT0` (because they | ||
are `unsafe` functions), use `asm!` or access registers. | ||
|
||
**Claim**: this program is well-defined / sound for any `Type`. Rust aliasing | ||
rules are respected because `main` is only invoked once thus `x` is the only | ||
reference to the static variable `X`. | ||
|
||
## Questions | ||
|
||
@RalfJung mentioned that that the `noalias` attribute in the LLVM IR could cause | ||
problems with this pattern because it tells LLVM that | ||
|
||
> it (the pointer) does not alias with anything that already exists, including | ||
> all global statics | ||
|
||
### `noalias` argument | ||
|
||
Would this code result in UB? | ||
|
||
``` rust | ||
#![no_std] | ||
|
||
#[no_mangle] | ||
unsafe fn main() -> ! { | ||
unsafe { | ||
asm!("ENABLE_INTERRUPTS" : : : : "volatile"); | ||
} | ||
|
||
let x: &'static mut Type = unsafe { | ||
static mut X: Type = Type::new(); | ||
|
||
&mut X | ||
}; | ||
|
||
foo(x); | ||
|
||
loop {} | ||
} | ||
|
||
fn foo(noalias_pointer: &mut Type) { | ||
// .. any safe code .. | ||
} | ||
``` | ||
|
||
`noalias_pointer` is marked as `noalias` in the LLVM IR so it must *not* alias | ||
with global statics like `X` but in this program `noalias_pointer` *is* a | ||
reference to the static variable `X`. | ||
|
||
### `noalias` return value | ||
|
||
What about this one where `Type` is `[u8; 1024]`? | ||
|
||
``` rust | ||
#![no_std] | ||
|
||
struct BumpPointerAlloc { | ||
ptr: *mut u8, | ||
space_left: usize, | ||
} | ||
|
||
impl BumpPointerAlloc { | ||
fn new(memory: &'static mut [u8]) -> Self { | ||
BumpPointerAlloc { | ||
ptr: memory.as_mut_ptr(), | ||
space_left: memory.len(), | ||
} | ||
} | ||
} | ||
|
||
unsafe impl GlobalAlloc for BumpPointerAlloc { | ||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 { | ||
// returns a non-null, aligned pointer if there's enough space left | ||
// otherwise returns a null pointer | ||
} | ||
|
||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { | ||
// never deallocates | ||
} | ||
} | ||
|
||
unsafe fn main() -> ! { | ||
let x: &'static mut [u8; 1024] = unsafe { | ||
static mut X: [u8; 1024] = [0; 1024]; | ||
|
||
&mut X | ||
}; | ||
|
||
let alloc = BumpPointerAlloc::new(x); | ||
let noalias_pointer: &'static mut u32 = unsafe { | ||
&mut *(alloc.alloc(Layout::from_size_align(4, 4)) as *mut u32) | ||
}; | ||
|
||
loop { | ||
// .. any safe code .. | ||
} | ||
} | ||
``` | ||
|
||
The return value of `GlobalAlloc::alloc` is marked as `noalias` meaning that | ||
`noalias_pointer` should not overlap with a static like `X` but in this case it | ||
does overlap with `X`. | ||
|
||
(By the way, using a `&'static mut [u8]` that references a static variable as the | ||
"memory pool" / "backend storage" of (global) allocators is not uncommon in | ||
embedded programs -- there's no `brk` / `sbrk` after all.) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an example for something that would not be okay here: even just reading from
x
orX
will invalidate overlapping parts ofnoalias_pointer
. So if the implementation ofBumpPointerAlloc
would do that, that would be a problem.