Why does stack pinning (e.g. `pin_mut!()`) allocate memory?
tl;dr
This:
#![feature(bench_black_box)]
use core::hint::black_box;
#[inline(never)]
pub fn foo<T>(mut val: T) {
loop {
black_box(&mut val);
}
}
pub fn bar() {
foo([0u8; 1024]);
}
... compiles to:
define ... {
start:
%0 = alloca [1024 x i8]*, align 8
%1 = bitcast [1024 x i8]** %0 to i8*
/* ... */
}
... but pinning val to the stack:
#[inline(never)]
pub fn foo<T>(val: T) {
let mut val = val;
loop {
black_box(&mut val);
}
}
... causes rustc to emit a memcpy:
define ... {
start:
%0 = alloca [1024 x i8]*, align 8
%val1 = alloca [1024 x i8], align 1
%1 = getelementptr inbounds [1024 x i8], [1024 x i8]* %val1, i64 0, i64 0
%2 = getelementptr inbounds [1024 x i8], [1024 x i8]* %val, i64 0, i64 0
call void @llvm.memcpy.p0i8.p0i8.i64(...)
%3 = bitcast [1024 x i8]** %0 to i8*
br label %bb1
}
... which, to me, seems overzealous, since the value already lives on the stack; this copy doesn't seem to accomplish anything besides causing the function to eat more stack space.
Now, I understand that this copying might be necessary sometimes (I guess if I were to Box or Rc that value?), but in there it seems futile - can that be just a missed optimization opportunity or there's something fundamental I'm not seeing? :-)
Obligatory: tested on --release, current nightly & from a few months ago.
Context:
I'm implementing an async executor for an AVR Rust project I'm doing:
impl Runtime {
/* ... */
pub fn enter(self, main: impl Future<Output = !>) -> ! {
/* ... */
pin_mut!(main);
loop {
match main.as_mut().poll(&mut context) {
Poll::Ready(_) => {
// Unreachable
}
Poll::Pending => {
//
}
}
}
}
}
... and I saw that when it's given a "large" future, it triggers a CPU-reset.
After a while I've nailed the reset down to a stack overflow (after all, there's just 2k of RAM), but measuring the future's size (~800 bytes) suggested that everything should be working correctly - I ain't using that much memory.
Eventually I've changed pin_mut!() to:
impl Runtime {
/* ... */
pub fn enter(self, mut main: impl Future<Output = !>) -> ! {
/* ... */
let mut main = unsafe { Pin::new_unchecked(&mut main) };
/* ... */
}
}
... which caused my application to work correctly, suggesting to me (which I also confirmed with gdb) that pin_mut!() triggers a memcpy.