Reputation: 675
How can I ensure that a function with no side effects gets executed and doesn't get optimized away in stable Rust?
Is there an attribute combination I could use, or must I call another function with side effects? In the case where a function call is necessary, does the Rust standard library provide cheap function that is guaranteed to not be optimized away?
Upvotes: 6
Views: 7511
Reputation: 1469
In 1.66, you can use:
core::hint::black_box(&dummy)
core::ptr::read_volatile(&dummy)
Upvotes: 3
Reputation: 83
As long as std::hint:black_box
is still unstable, you could implement something similar (but probably less efficient) yourself using std::ptr::read_volatile
. As with std::hint::black_box
, calling this with an otherwise unused value should prevent that value from being optimized away:
fn black_box<T>(dummy: T) -> T {
unsafe {
std::ptr::read_volatile(&dummy)
}
}
Upvotes: 4
Reputation: 1521
#[no_mangle]
will currently do this, but that may change.
#[no_mangle]
pub fn do_what_i_say_dammit(x: i64) -> i64 { x*x }
To clarify (from that post):
My mental model is that symbols are owned by rustc by default (e.g., if the symbol is private, rustc can emit a differently-typed "arg-promoted" symbol instead of the expected one, as long as it handles it correctly), and
#[no_mangle]
transfers ownership of the symbol to the programmer.Now, because ownership is transferred to the programmer, rustc's unspecified symbol mangling scheme can't be used, so the symbol is left unmangled.
Having an unmangled rustc-owned symbol makes pretty much no sense (you can't actually use it because the compiler owns it) - so
#[no_mangle]
implies#[linker_owned]
. There is no loose#[linker_owned]
because nobody implemented it.
Edit: Here's a simple example.
Upvotes: 3
Reputation: 88886
There is test::black_box()
(link to old docs) which is still unstable (as is the whole test
crate). This function takes a value of an arbitrary type and returns the same value again. So it is basically the identity function. "Oh well, now that's very useful, isn't it?" you might ask ironically.
But there is something special: the value which is passed through is hidden from LLVM (the thing doing nearly all optimizations in Rust right now)! It's truly a black box as LLVM doesn't know anything about a piece of code. And without knowing anything LLVM can't prove that optimizations won't be changing the program's behavior. Thus: no optimizations.
How does it do that? Let's look at the definition:
pub fn black_box<T>(dummy: T) -> T {
// we need to "use" the argument in some way LLVM can't
// introspect.
unsafe { asm!("" : : "r"(&dummy)) }
dummy
}
I'd be lying if I were to pretend I understand this piece of code completely, but it goes something like that: we insert empty inline assembly (not a single instruction) but tell Rust (which tells LLVM) that this piece of assembly uses the variable dummy
. This makes it impossible for the optimizer to reason about the variable. Stupid compiler, so easy to deceive, muhahahaha! If you want another explanation, Chandler Carruth explained the dark magic at CppCon 2015.
So how do you use it now? Just use it for some kind of value... anything that goes through black_box()
needs to be calculated. How about something like this?
black_box(my_function());
The return value of my_function()
needs to be calculated, because the compiler can't prove it's useless! So the function call won't be removed. Note however, that you have to use unstable features (either the test
crate or inline asm to write the function yourself) or use FFI. I certainly wouldn't ship this kind of code in a production library, but it's certainly useful for testing purposes!
Upvotes: 2