Skip to content

Commit 5327e78

Browse files
committed
rustc_codegen_ssa: Use llvm.invariant intrinsics on arguments that are
immutable references; off by default. Optimization failures around reloads and memcpy optimizations are frequently traceable to LLVM's failure to prove that memory can't be mutated by a call or store. This problem is especially acute in Rust, where large values tend to be memcpy'd more often than in C++. Thankfully, Rust has stronger guarantees on mutability available than C++ does, via the strong immutability of `&` references. This should allow LLVM to prove that memory can't be modified by stores and calls in more cases. We're already using LLVM's `readonly` parameter attribute on such calls. However, the semantics of `readonly` are akin to `const` in C++, in that they only promise to LLVM that the function won't mutate the parameter *through that pointer*, not that the pointed-to memory is immutable for the entire duration of the function. These weak semantics limit the applicability of `readonly` to LLVM's alias analysis. Instead of `readonly`, the correct way to express strong immutability guarantees on memory is through the `llvm.invariant.start` and `llvm.invariant.end` intrinsics. These enable a frontend like `rustc` to describe immutability of memory regions in an expressive, flow-sensitive manner. Unfortunately, LLVM doesn't use the `llvm.invariant.start` and `llvm.invariant.end` intrinsics for much at the moment. It's only used in one optimization in loop-invariant code motion at this time. Follow-up work will need to be done in LLVM to integrate these intrinsics into alias analysis. Possibly there will need to be some sort of "MemoryInvarianceAnalysis" that uses graph reachability algorithms to analyze the extent of the guarantees provided by these intrinsics to the control flow graph. Regardless, this front-end work needs to happen as a prerequisite for any LLVM work, so that the improvements to LLVM can be measured and tested. So this commit makes `rustc` use `llvm.invariant` in a minimal way: on immutable references to "freeze" types (i.e. not transitively containing UnsafeCell) passed directly as parameters to functions. This is off by default, gated behind the non-default `-Z emit-invariant-markers=yes` flag. Obviously, a lot more can be done to use `llvm.invariant` more liberally in the future, but this can be added over time, especially once more LLVM optimization passes use that infrastructure. This is simply the bare minimum for now. Once LLVM uses those intrinsics for more optimizations, the effects of more `llvm.invariant` use can be measured more precisely.
1 parent bf15a9e commit 5327e78

File tree

9 files changed

+101
-3
lines changed

9 files changed

+101
-3
lines changed

compiler/rustc_codegen_gcc/src/builder.rs

+10
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,16 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
12351235
// TODO(antoyo)
12361236
}
12371237

1238+
fn invariant_start(&mut self, _value_ptr: RValue<'gcc>, _size: Size) -> RValue<'gcc> {
1239+
// TODO
1240+
unimplemented!()
1241+
}
1242+
1243+
fn invariant_end(&mut self, _marker_ptr: RValue<'gcc>, _value_ptr: RValue<'gcc>, _size: Size) {
1244+
// TODO
1245+
unimplemented!()
1246+
}
1247+
12381248
fn call(
12391249
&mut self,
12401250
_typ: Type<'gcc>,

compiler/rustc_codegen_llvm/src/builder.rs

+15
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,21 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
11131113
self.call_lifetime_intrinsic("llvm.lifetime.end.p0i8", ptr, size);
11141114
}
11151115

1116+
fn invariant_start(&mut self, value_ptr: &'ll Value, size: Size) -> &'ll Value {
1117+
let size = size.bytes();
1118+
let value_ptr = self.pointercast(value_ptr, self.cx.type_i8p());
1119+
self.call_intrinsic("llvm.invariant.start.p0i8", &[self.cx.const_u64(size), value_ptr])
1120+
}
1121+
1122+
fn invariant_end(&mut self, marker_ptr: &'ll Value, value_ptr: &'ll Value, size: Size) {
1123+
let size = size.bytes();
1124+
let value_ptr = self.pointercast(value_ptr, self.cx.type_i8p());
1125+
self.call_intrinsic(
1126+
"llvm.invariant.end.p0i8",
1127+
&[marker_ptr, self.cx.const_u64(size), value_ptr],
1128+
);
1129+
}
1130+
11161131
fn instrprof_increment(
11171132
&mut self,
11181133
fn_name: &'ll Value,

compiler/rustc_codegen_llvm/src/context.rs

+4
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ impl<'ll> CodegenCx<'ll, '_> {
629629
}
630630

631631
let i8p = self.type_i8p();
632+
let unitp = self.type_ptr_to(self.type_struct(&[], false));
632633
let void = self.type_void();
633634
let i1 = self.type_i1();
634635
let t_i8 = self.type_i8();
@@ -840,6 +841,9 @@ impl<'ll> CodegenCx<'ll, '_> {
840841
ifn!("llvm.lifetime.start.p0i8", fn(t_i64, i8p) -> void);
841842
ifn!("llvm.lifetime.end.p0i8", fn(t_i64, i8p) -> void);
842843

844+
ifn!("llvm.invariant.start.p0i8", fn(t_i64, i8p) -> unitp);
845+
ifn!("llvm.invariant.end.p0i8", fn(unitp, t_i64, i8p) -> void);
846+
843847
ifn!("llvm.expect.i1", fn(i1, i1) -> i1);
844848
ifn!("llvm.eh.typeid.for", fn(i8p) -> t_i32);
845849
ifn!("llvm.localescape", fn(...) -> void);

compiler/rustc_codegen_ssa/src/mir/block.rs

+16
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> {
249249
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
250250
/// Generates code for a `Resume` terminator.
251251
fn codegen_resume_terminator(&mut self, helper: TerminatorCodegenHelper<'tcx>, mut bx: Bx) {
252+
self.emit_invariant_end_markers(&mut bx);
253+
252254
if let Some(funclet) = helper.funclet(self) {
253255
bx.cleanup_ret(funclet, None);
254256
} else {
@@ -306,6 +308,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
306308
}
307309

308310
fn codegen_return_terminator(&mut self, mut bx: Bx) {
311+
self.emit_invariant_end_markers(&mut bx);
312+
309313
// Call `va_end` if this is the definition of a C-variadic function.
310314
if self.fn_abi.c_variadic {
311315
// The `VaList` "spoofed" argument is just after all the real arguments.
@@ -1723,6 +1727,18 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
17231727
}
17241728
}
17251729

1730+
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
1731+
fn emit_invariant_end_markers(&mut self, bx: &mut Bx) {
1732+
for invariant_value in self.invariant_values.iter().rev() {
1733+
bx.invariant_end(
1734+
invariant_value.marker_ptr,
1735+
invariant_value.value_ptr,
1736+
invariant_value.size,
1737+
)
1738+
}
1739+
}
1740+
}
1741+
17261742
enum ReturnDest<'tcx, V> {
17271743
// Do nothing; the return value is indirect or ignored.
17281744
Nothing,

compiler/rustc_codegen_ssa/src/mir/mod.rs

+38-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use crate::traits::*;
2-
use rustc_middle::mir;
32
use rustc_middle::mir::interpret::ErrorHandled;
4-
use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt, TyAndLayout};
3+
use rustc_middle::mir::{self, Mutability};
4+
use rustc_middle::ty::layout::{FnAbiOf, HasTyCtxt, LayoutOf, TyAndLayout};
55
use rustc_middle::ty::{self, Instance, Ty, TypeFoldable, TypeVisitable};
6-
use rustc_target::abi::call::{FnAbi, PassMode};
6+
use rustc_span::DUMMY_SP;
7+
use rustc_target::abi::call::{ArgAbi, FnAbi, PassMode};
8+
use rustc_target::abi::Size;
79

810
use std::iter;
911

@@ -87,6 +89,11 @@ pub struct FunctionCx<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> {
8789

8890
/// Caller location propagated if this function has `#[track_caller]`.
8991
caller_location: Option<OperandRef<'tcx, Bx::Value>>,
92+
93+
/// Information about pointers that are known immutable for the entire execution of the
94+
/// function. We need to keep this information around so that we can call `invariant_end()`
95+
/// before exiting this function.
96+
invariant_values: Vec<InvariantValue<Bx::Value>>,
9097
}
9198

9299
impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
@@ -103,6 +110,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
103110
}
104111
}
105112

113+
/// Information about pointers that are known immutable for the entire execution of the
114+
/// function. We need to keep this information around so that we can call `invariant_end()`
115+
/// before exiting this function.
116+
struct InvariantValue<V> {
117+
marker_ptr: V,
118+
value_ptr: V,
119+
size: Size,
120+
}
121+
106122
enum LocalRef<'tcx, V> {
107123
Place(PlaceRef<'tcx, V>),
108124
/// `UnsizedPlace(p)`: `p` itself is a thin pointer (indirect place).
@@ -178,6 +194,7 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
178194
debug_context,
179195
per_local_var_debug_info: None,
180196
caller_location: None,
197+
invariant_values: vec![],
181198
};
182199

183200
fx.per_local_var_debug_info = fx.compute_per_local_var_debug_info(&mut bx);
@@ -325,6 +342,7 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
325342
PassMode::Direct(_) => {
326343
let llarg = bx.get_param(llarg_idx);
327344
llarg_idx += 1;
345+
emit_invariant_markers_for(bx, fx, arg, llarg);
328346
return local(OperandRef::from_immediate_or_packed_pair(
329347
bx, llarg, arg.layout,
330348
));
@@ -398,6 +416,23 @@ fn arg_local_refs<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
398416
args
399417
}
400418

419+
fn emit_invariant_markers_for<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
420+
bx: &mut Bx,
421+
fx: &mut FunctionCx<'a, 'tcx, Bx>,
422+
arg: &ArgAbi<'tcx, Ty<'tcx>>,
423+
value_ptr: Bx::Value,
424+
) {
425+
if bx.sess().opts.unstable_opts.emit_invariant_markers {
426+
if let ty::Ref(_, subty, Mutability::Not) = arg.layout.ty.kind() {
427+
if subty.is_freeze(bx.tcx().at(DUMMY_SP), ty::ParamEnv::reveal_all()) {
428+
let size = bx.cx().layout_of(*subty).size;
429+
let marker_ptr = bx.invariant_start(value_ptr, size);
430+
fx.invariant_values.push(InvariantValue { marker_ptr, value_ptr, size });
431+
}
432+
}
433+
}
434+
}
435+
401436
mod analyze;
402437
mod block;
403438
pub mod constant;

compiler/rustc_codegen_ssa/src/traits/builder.rs

+3
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,9 @@ pub trait BuilderMethods<'a, 'tcx>:
310310
/// Called for `StorageDead`
311311
fn lifetime_end(&mut self, ptr: Self::Value, size: Size);
312312

313+
fn invariant_start(&mut self, ptr: Self::Value, size: Size) -> Self::Value;
314+
fn invariant_end(&mut self, marker_ptr: Self::Value, value_ptr: Self::Value, size: Size);
315+
313316
fn instrprof_increment(
314317
&mut self,
315318
fn_name: Self::Value,

compiler/rustc_session/src/options.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,8 @@ options! {
12951295
an additional `.html` file showing the computed coverage spans."),
12961296
dwarf_version: Option<u32> = (None, parse_opt_number, [TRACKED],
12971297
"version of DWARF debug information to emit (default: 2 or 4, depending on platform)"),
1298+
emit_invariant_markers: bool = (false, parse_bool, [UNTRACKED],
1299+
"emit `llvm.invariant` markers, which may enable better optimization (default: no)"),
12981300
emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED],
12991301
"emit a section containing stack size metadata (default: no)"),
13001302
emit_thin_lto: bool = (true, parse_bool, [TRACKED],

src/test/codegen/invariant-basic.rs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// compile-flags: -C opt-level=0 -Z emit-invariant-markers=yes
2+
#![crate_type="lib"]
3+
4+
#[no_mangle]
5+
pub fn foo(x: &i32) {
6+
// CHECK-LABEL: @foo
7+
// CHECK: call {{.*}} @llvm.invariant.start{{[^(]*}}(i64 4
8+
// CHECK: call void @{{.*}}drop{{.*}}
9+
// CHECK: call void @llvm.invariant.end
10+
// CHECK: ret void
11+
drop(x);
12+
}

src/test/rustdoc-ui/z-help.stdout

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
-Z dump-mir-graphviz=val -- in addition to `.mir` files, create graphviz `.dot` files (and with `-Z instrument-coverage`, also create a `.dot` file for the MIR-derived coverage graph) (default: no)
3737
-Z dump-mir-spanview=val -- in addition to `.mir` files, create `.html` files to view spans for all `statement`s (including terminators), only `terminator` spans, or computed `block` spans (one span encompassing a block's terminator and all statements). If `-Z instrument-coverage` is also enabled, create an additional `.html` file showing the computed coverage spans.
3838
-Z dwarf-version=val -- version of DWARF debug information to emit (default: 2 or 4, depending on platform)
39+
-Z emit-invariant-markers=val -- emit `llvm.invariant` markers, which may enable better optimization (default: no)
3940
-Z emit-stack-sizes=val -- emit a section containing stack size metadata (default: no)
4041
-Z emit-thin-lto=val -- emit the bc module with thin LTO info (default: yes)
4142
-Z export-executable-symbols=val -- export symbols from executables, as if they were dynamic libraries

0 commit comments

Comments
 (0)