I was writing a longer post about how noalias can be used, but in the meantime, fI think I found a case (for real this time
) where your semantics don’t justify a current optimization. It’s not important for this optimization to occur, and I think switching to the newer versions of noalias would allow rustc to prevent it without affecting most real-world code (switching would also allow improving optimization potential in many other ways). Nevertheless, I thought it was worth posting, if only as a demonstration that your semantics wouldn’t be 100% compatible with rustc as-is.
Here it is:
#![feature(test)]
fn main() {
let boks = Box::new(43);
let raw = &*boks as *const i32 as *mut i32;
println!("{}", foo(boks, raw));
}
#[inline(never)]
fn foo(mut boks: Box<i32>, raw: *mut i32) -> i32 {
let a = *boks; // load #1
m::bar(&mut *boks, raw);
let b = *boks; // load #2, boks is assumed unchanged
a - b
}
mod m {
use std::mem::drop;
extern crate test;
#[inline(always)]
pub fn bar(reff: &mut i32, raw: *mut i32) {
if reff as *mut i32 != raw {
// panic without LLVM knowing this must panic
test::black_box(panic_please as fn())();
}
drop({reff}); // get rid of the reference
unsafe { *raw = 1; }
}
fn panic_please() { panic!(); }
}
Basically, foo is called with a Box and a raw pointer, both pointing to the same place. However, rustc gives the Box noalias metadata (the old argument version), which has the semantics that load/stores to pointers ‘based on’ it should not alias with stores to pointers not ‘based on’ it, until the function is done executing. Thus, a store to the raw pointer would be UB.
The tricky part is doing a store to the raw pointer without also violating your rules: the reference must not be write locked at that time, and any unsafe code must be separated into its own function, so that any potential deoptimizations that might apply to unsafe functions or modules would not extend to the problematic optimization.
For the former, I reborrow the box when passing it to bar, which should suspend foo's write lock; bar then calls drop, moving the reference, to release its own write lock, before calling baz.
For the latter, the noalias metadata is generated on foo, while only baz contains unsafe code. The if at the beginning is just to make it ‘well-behaved’ as a safe exported function, i.e. safe code shouldn’t be able to cause bad behavior by calling it. That’s only necessary in the first place if baz is required to be exported, i.e. if the presence of unsafe code deoptimizes entire modules. (Note: In this version, it is necessary for LLVM to inline bar in order to perform the problematic optimization. If either unsafe doesn’t deoptimize entire modules, or you drop the requirement that public functions be statically [as opposed to dynamically] well-behaved, then it’s not necessary for unsafe code to be inlined at all.)
Anyway, it should print 0 if you compile it with optimizations and 42 without optimizations. This is because LLVM coalesces the two *boks loads into one.
This could be avoided by switching from old noalias to either scoped noalias metadata or the upcoming noalias intrinsic, both of which assert that loads are non-aliasing only with respect to specified instructions (as opposed to absolutely everything). Where a mutable reference or box (or a load/store of one) is marked noalias, rustc could exclude, from ‘specified instructions’, any call instructions that take place while there’s an active reborrow of that reference which might be visible to that call (i.e. passed as an argument or otherwise escaping).
In practice, that would usually be an argument, and in normal code it wouldn’t be possible anyway to rule out writes to that argument (because normally, functions that take mutable references do so for a reason) - so such a change shouldn’t generally cause performance loss.
(Oh, boy, I hope I didn’t write this up only to be missing an obvious reason the code would be illegal
)