So @eddyb and me talked about this, and this comment summarizes that.
@eddyb proposes a bench_input
function with the operational semantics of the identity function, that the optimizer is encouraged / hinted to treat as an unknown pure function (think extern { const fn bench_input<T>(T) -> T; }
).
That is, the statement bench_input(expr);
can be completely optimized away. Given:
let mut v = Vec::with_capacity(1);
v.push(1);
let v = bench_input(v);
v[0];
the bound check in v[0]
cannot be removed because bench_input
could return any vector (e.g. Vec::new()
). Also, because the return value depends on the input, the write of 1
to memory in the push
must be flushed before calling bench_input
.
However, because the v[0];
result is not used, that could be optimized away, and because v
is not used, and bench_input
is pure, actually the whole snippet can be optimized away.
To prevent that from happening, @eddyb proposes to add bench_output(x)
, which has the same operational semantics as mem::forget
: it leaks its value, runs no destructors, but which the optimizer is encouraged to treat as an unknown function with read-only side-effects that depend on x
.
With that, one can change the snippet above to:
let mut v = Vec::with_capacity(1);
v.push(1);
let v = bench_input(v);
bench_output(v[0]);
to prevent the code from being removed.
Note: bench_output
cannot be implemented as fn bench_output<T>(x: T) { bench_input(x); }
, because that would mean that it can be removed. It must therefore be a special API.
Something like black_box
, as proposed in this RFC, would then be implemented as:
fn black_box<T>(x: T) -> T {
bench_output(&x);
bench_input(x)
}