Skip to content

Commit f37aa99

Browse files
committed
Auto merge of #147890 - tmiasko:deduce-captures-none, r=cjgillot
Deduce captures(none) for a return place and parameters Extend attribute deduction to determine whether parameters using indirect pass mode might have their address captured. Similarly to the deduction of `readonly` attribute this information facilitates memcpy optimizations.
2 parents f977dfc + 2a03a94 commit f37aa99

File tree

17 files changed

+332
-180
lines changed

17 files changed

+332
-180
lines changed

compiler/rustc_codegen_llvm/src/abi.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,11 @@ trait ArgAttributesExt {
3939
const ABI_AFFECTING_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 1] =
4040
[(ArgAttribute::InReg, llvm::AttributeKind::InReg)];
4141

42-
const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 6] = [
42+
const OPTIMIZATION_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 4] = [
4343
(ArgAttribute::NoAlias, llvm::AttributeKind::NoAlias),
44-
(ArgAttribute::CapturesAddress, llvm::AttributeKind::CapturesAddress),
4544
(ArgAttribute::NonNull, llvm::AttributeKind::NonNull),
4645
(ArgAttribute::ReadOnly, llvm::AttributeKind::ReadOnly),
4746
(ArgAttribute::NoUndef, llvm::AttributeKind::NoUndef),
48-
(ArgAttribute::CapturesReadOnly, llvm::AttributeKind::CapturesReadOnly),
4947
];
5048

5149
fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'ll Attribute; 8]> {
@@ -81,15 +79,23 @@ fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'
8179
}
8280
for (attr, llattr) in OPTIMIZATION_ATTRIBUTES {
8381
if regular.contains(attr) {
84-
// captures(...) is only available since LLVM 21.
85-
if (attr == ArgAttribute::CapturesReadOnly || attr == ArgAttribute::CapturesAddress)
86-
&& llvm_util::get_version() < (21, 0, 0)
87-
{
88-
continue;
89-
}
9082
attrs.push(llattr.create_attr(cx.llcx));
9183
}
9284
}
85+
// captures(...) is only available since LLVM 21.
86+
if (21, 0, 0) <= llvm_util::get_version() {
87+
const CAPTURES_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 3] = [
88+
(ArgAttribute::CapturesNone, llvm::AttributeKind::CapturesNone),
89+
(ArgAttribute::CapturesAddress, llvm::AttributeKind::CapturesAddress),
90+
(ArgAttribute::CapturesReadOnly, llvm::AttributeKind::CapturesReadOnly),
91+
];
92+
for (attr, llattr) in CAPTURES_ATTRIBUTES {
93+
if regular.contains(attr) {
94+
attrs.push(llattr.create_attr(cx.llcx));
95+
break;
96+
}
97+
}
98+
}
9399
} else if cx.tcx.sess.opts.unstable_opts.sanitizer.contains(SanitizerSet::MEMORY) {
94100
// If we're not optimising, *but* memory sanitizer is on, emit noundef, since it affects
95101
// memory sanitizer's behavior.

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ pub(crate) enum AttributeKind {
289289
DeadOnUnwind = 43,
290290
DeadOnReturn = 44,
291291
CapturesReadOnly = 45,
292+
CapturesNone = 46,
292293
}
293294

294295
/// LLVMIntPredicate

compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ enum class LLVMRustAttributeKind {
245245
DeadOnUnwind = 43,
246246
DeadOnReturn = 44,
247247
CapturesReadOnly = 45,
248+
CapturesNone = 46,
248249
};
249250

250251
static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
@@ -339,6 +340,7 @@ static Attribute::AttrKind fromRust(LLVMRustAttributeKind Kind) {
339340
#endif
340341
case LLVMRustAttributeKind::CapturesAddress:
341342
case LLVMRustAttributeKind::CapturesReadOnly:
343+
case LLVMRustAttributeKind::CapturesNone:
342344
report_fatal_error("Should be handled separately");
343345
}
344346
report_fatal_error("bad LLVMRustAttributeKind");
@@ -390,6 +392,9 @@ extern "C" void LLVMRustEraseInstFromParent(LLVMValueRef Instr) {
390392
extern "C" LLVMAttributeRef
391393
LLVMRustCreateAttrNoValue(LLVMContextRef C, LLVMRustAttributeKind RustAttr) {
392394
#if LLVM_VERSION_GE(21, 0)
395+
if (RustAttr == LLVMRustAttributeKind::CapturesNone) {
396+
return wrap(Attribute::getWithCaptureInfo(*unwrap(C), CaptureInfo::none()));
397+
}
393398
if (RustAttr == LLVMRustAttributeKind::CapturesAddress) {
394399
return wrap(Attribute::getWithCaptureInfo(
395400
*unwrap(C), CaptureInfo(CaptureComponents::Address)));

compiler/rustc_middle/src/middle/deduced_param_attrs.rs

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@ use rustc_macros::{Decodable, Encodable, HashStable};
22

33
use crate::ty::{Ty, TyCtxt, TypingEnv};
44

5-
/// Flags that dictate how a parameter is mutated. If the flags are empty, the param is
6-
/// read-only. If non-empty, it is read-only if *all* flags' conditions are met.
5+
/// Summarizes how a parameter (a return place or an argument) is used inside a MIR body.
76
#[derive(Clone, Copy, PartialEq, Debug, Decodable, Encodable, HashStable)]
8-
pub struct DeducedReadOnlyParam(u8);
7+
pub struct UsageSummary(u8);
98

109
bitflags::bitflags! {
11-
impl DeducedReadOnlyParam: u8 {
12-
/// This parameter is dropped. It is read-only if `!needs_drop`.
13-
const IF_NO_DROP = 1 << 0;
14-
/// This parameter is borrowed. It is read-only if `Freeze`.
15-
const IF_FREEZE = 1 << 1;
16-
/// This parameter is mutated. It is never read-only.
17-
const MUTATED = 1 << 2;
10+
impl UsageSummary: u8 {
11+
/// This parameter is dropped when it `needs_drop`.
12+
const DROP = 1 << 0;
13+
/// There is a shared borrow to this parameter.
14+
/// It allows for mutation unless parameter is `Freeze`.
15+
const SHARED_BORROW = 1 << 1;
16+
/// This parameter is mutated (excluding through a drop or a shared borrow).
17+
const MUTATE = 1 << 2;
18+
/// This parameter is captured (excluding through a drop).
19+
const CAPTURE = 1 << 3;
1820
}
1921
}
2022

@@ -24,43 +26,53 @@ bitflags::bitflags! {
2426
/// These can be useful for optimization purposes when a function is directly called. We compute
2527
/// them and store them into the crate metadata so that downstream crates can make use of them.
2628
///
27-
/// Right now, we only have `read_only`, but `no_capture` and `no_alias` might be useful in the
29+
/// Right now, we have `readonly` and `captures(none)`, but `no_alias` might be useful in the
2830
/// future.
2931
#[derive(Clone, Copy, PartialEq, Debug, Decodable, Encodable, HashStable)]
3032
pub struct DeducedParamAttrs {
31-
/// The parameter is marked immutable in the function.
32-
pub read_only: DeducedReadOnlyParam,
33-
}
34-
35-
// By default, consider the parameters to be mutated.
36-
impl Default for DeducedParamAttrs {
37-
#[inline]
38-
fn default() -> DeducedParamAttrs {
39-
DeducedParamAttrs { read_only: DeducedReadOnlyParam::MUTATED }
40-
}
33+
pub usage: UsageSummary,
4134
}
4235

4336
impl DeducedParamAttrs {
37+
/// Returns true if no attributes have been deduced.
4438
#[inline]
4539
pub fn is_default(self) -> bool {
46-
self.read_only.contains(DeducedReadOnlyParam::MUTATED)
40+
self.usage.contains(UsageSummary::MUTATE | UsageSummary::CAPTURE)
4741
}
4842

43+
/// For parameters passed indirectly, returns true if pointer is never written through.
4944
pub fn read_only<'tcx>(
5045
&self,
5146
tcx: TyCtxt<'tcx>,
5247
typing_env: TypingEnv<'tcx>,
5348
ty: Ty<'tcx>,
5449
) -> bool {
55-
let read_only = self.read_only;
56-
// We have to check *all* set bits; only if all checks pass is this truly read-only.
57-
if read_only.contains(DeducedReadOnlyParam::MUTATED) {
50+
// Only if all checks pass is this truly read-only.
51+
if self.usage.contains(UsageSummary::MUTATE) {
52+
return false;
53+
}
54+
if self.usage.contains(UsageSummary::DROP) && ty.needs_drop(tcx, typing_env) {
55+
return false;
56+
}
57+
if self.usage.contains(UsageSummary::SHARED_BORROW) && !ty.is_freeze(tcx, typing_env) {
5858
return false;
5959
}
60-
if read_only.contains(DeducedReadOnlyParam::IF_NO_DROP) && ty.needs_drop(tcx, typing_env) {
60+
true
61+
}
62+
63+
/// For parameters passed indirectly, returns true if pointer is not captured, i.e., its
64+
/// address is not captured, and pointer is used neither for reads nor writes after function
65+
/// returns.
66+
pub fn captures_none<'tcx>(
67+
&self,
68+
tcx: TyCtxt<'tcx>,
69+
typing_env: TypingEnv<'tcx>,
70+
ty: Ty<'tcx>,
71+
) -> bool {
72+
if self.usage.contains(UsageSummary::CAPTURE) {
6173
return false;
6274
}
63-
if read_only.contains(DeducedReadOnlyParam::IF_FREEZE) && !ty.is_freeze(tcx, typing_env) {
75+
if self.usage.contains(UsageSummary::DROP) && ty.needs_drop(tcx, typing_env) {
6476
return false;
6577
}
6678
true

compiler/rustc_mir_transform/src/deduce_param_attrs.rs

Lines changed: 67 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,65 +11,91 @@
1111
1212
use rustc_hir::def_id::LocalDefId;
1313
use rustc_index::IndexVec;
14-
use rustc_middle::middle::deduced_param_attrs::{DeducedParamAttrs, DeducedReadOnlyParam};
14+
use rustc_middle::middle::deduced_param_attrs::{DeducedParamAttrs, UsageSummary};
1515
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
1616
use rustc_middle::mir::*;
1717
use rustc_middle::ty::{self, Ty, TyCtxt};
1818
use rustc_session::config::OptLevel;
1919

20-
/// A visitor that determines which arguments have been mutated. We can't use the mutability field
21-
/// on LocalDecl for this because it has no meaning post-optimization.
22-
struct DeduceReadOnly {
23-
/// Each bit is indexed by argument number, starting at zero (so 0 corresponds to local decl
24-
/// 1). The bit is false if the argument may have been mutated or true if we know it hasn't
25-
/// been up to the point we're at.
26-
read_only: IndexVec<usize, DeducedReadOnlyParam>,
20+
/// A visitor that determines how a return place and arguments are used inside MIR body.
21+
/// To determine whether a local is mutated we can't use the mutability field on LocalDecl
22+
/// because it has no meaning post-optimization.
23+
struct DeduceParamAttrs {
24+
/// Summarizes how a return place and arguments are used inside MIR body.
25+
usage: IndexVec<Local, UsageSummary>,
2726
}
2827

29-
impl DeduceReadOnly {
30-
/// Returns a new DeduceReadOnly instance.
31-
fn new(arg_count: usize) -> Self {
32-
Self { read_only: IndexVec::from_elem_n(DeducedReadOnlyParam::empty(), arg_count) }
28+
impl DeduceParamAttrs {
29+
/// Returns a new DeduceParamAttrs instance.
30+
fn new(body: &Body<'_>) -> Self {
31+
let mut this =
32+
Self { usage: IndexVec::from_elem_n(UsageSummary::empty(), body.arg_count + 1) };
33+
// Code generation indicates that a return place is writable. To avoid setting both
34+
// `readonly` and `writable` attributes, when return place is never written to, mark it as
35+
// mutated.
36+
this.usage[RETURN_PLACE] |= UsageSummary::MUTATE;
37+
this
3338
}
3439

35-
/// Returns whether the given local is a parameter and its index.
36-
fn as_param(&self, local: Local) -> Option<usize> {
37-
// Locals and parameters are shifted by `RETURN_PLACE`.
38-
let param_index = local.as_usize().checked_sub(1)?;
39-
if param_index < self.read_only.len() { Some(param_index) } else { None }
40+
/// Returns whether a local is the return place or an argument and returns its index.
41+
fn as_param(&self, local: Local) -> Option<Local> {
42+
if local.index() < self.usage.len() { Some(local) } else { None }
4043
}
4144
}
4245

43-
impl<'tcx> Visitor<'tcx> for DeduceReadOnly {
46+
impl<'tcx> Visitor<'tcx> for DeduceParamAttrs {
4447
fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
45-
// We're only interested in arguments.
46-
let Some(param_index) = self.as_param(place.local) else { return };
48+
// We're only interested in the return place or an argument.
49+
let Some(i) = self.as_param(place.local) else { return };
4750

4851
match context {
49-
// Not mutating, so it's fine.
52+
// Not actually using the local.
5053
PlaceContext::NonUse(..) => {}
51-
// Dereference is not a mutation.
54+
// Neither mutated nor captured.
5255
_ if place.is_indirect_first_projection() => {}
5356
// This is a `Drop`. It could disappear at monomorphization, so mark it specially.
5457
PlaceContext::MutatingUse(MutatingUseContext::Drop)
5558
// Projection changes the place's type, so `needs_drop(local.ty)` is not
5659
// `needs_drop(place.ty)`.
5760
if place.projection.is_empty() => {
58-
self.read_only[param_index] |= DeducedReadOnlyParam::IF_NO_DROP;
61+
self.usage[i] |= UsageSummary::DROP;
62+
}
63+
PlaceContext::MutatingUse(
64+
MutatingUseContext::Call
65+
| MutatingUseContext::Yield
66+
| MutatingUseContext::Drop
67+
| MutatingUseContext::Borrow
68+
| MutatingUseContext::RawBorrow) => {
69+
self.usage[i] |= UsageSummary::MUTATE;
70+
self.usage[i] |= UsageSummary::CAPTURE;
71+
}
72+
PlaceContext::MutatingUse(
73+
MutatingUseContext::Store
74+
| MutatingUseContext::SetDiscriminant
75+
| MutatingUseContext::AsmOutput
76+
| MutatingUseContext::Projection
77+
| MutatingUseContext::Retag) => {
78+
self.usage[i] |= UsageSummary::MUTATE;
5979
}
60-
// This is a mutation, so mark it as such.
61-
PlaceContext::MutatingUse(..)
62-
// Whether mutating though a `&raw const` is allowed is still undecided, so we
63-
// disable any sketchy `readonly` optimizations for now.
6480
| PlaceContext::NonMutatingUse(NonMutatingUseContext::RawBorrow) => {
65-
self.read_only[param_index] |= DeducedReadOnlyParam::MUTATED;
81+
// Whether mutating though a `&raw const` is allowed is still undecided, so we
82+
// disable any sketchy `readonly` optimizations for now.
83+
self.usage[i] |= UsageSummary::MUTATE;
84+
self.usage[i] |= UsageSummary::CAPTURE;
6685
}
67-
// Not mutating if the parameter is `Freeze`.
6886
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) => {
69-
self.read_only[param_index] |= DeducedReadOnlyParam::IF_FREEZE;
87+
// Not mutating if the parameter is `Freeze`.
88+
self.usage[i] |= UsageSummary::SHARED_BORROW;
89+
self.usage[i] |= UsageSummary::CAPTURE;
7090
}
7191
// Not mutating, so it's fine.
72-
PlaceContext::NonMutatingUse(..) => {}
92+
PlaceContext::NonMutatingUse(
93+
NonMutatingUseContext::Inspect
94+
| NonMutatingUseContext::Copy
95+
| NonMutatingUseContext::Move
96+
| NonMutatingUseContext::FakeBorrow
97+
| NonMutatingUseContext::PlaceMention
98+
| NonMutatingUseContext::Projection) => {}
7399
}
74100
}
75101

@@ -98,11 +124,11 @@ impl<'tcx> Visitor<'tcx> for DeduceReadOnly {
98124
if let TerminatorKind::Call { ref args, .. } = terminator.kind {
99125
for arg in args {
100126
if let Operand::Move(place) = arg.node
101-
// We're only interested in arguments.
102-
&& let Some(param_index) = self.as_param(place.local)
103127
&& !place.is_indirect_first_projection()
128+
&& let Some(i) = self.as_param(place.local)
104129
{
105-
self.read_only[param_index] |= DeducedReadOnlyParam::MUTATED;
130+
self.usage[i] |= UsageSummary::MUTATE;
131+
self.usage[i] |= UsageSummary::CAPTURE;
106132
}
107133
}
108134
};
@@ -154,10 +180,9 @@ pub(super) fn deduced_param_attrs<'tcx>(
154180
if matches!(fn_ty.kind(), ty::FnDef(..))
155181
&& fn_ty
156182
.fn_sig(tcx)
157-
.inputs()
183+
.inputs_and_output()
158184
.skip_binder()
159185
.iter()
160-
.cloned()
161186
.all(type_will_always_be_passed_directly)
162187
{
163188
return &[];
@@ -170,13 +195,13 @@ pub(super) fn deduced_param_attrs<'tcx>(
170195

171196
// Grab the optimized MIR. Analyze it to determine which arguments have been mutated.
172197
let body: &Body<'tcx> = tcx.optimized_mir(def_id);
173-
let mut deduce_read_only = DeduceReadOnly::new(body.arg_count);
174-
deduce_read_only.visit_body(body);
175-
tracing::trace!(?deduce_read_only.read_only);
198+
let mut deduce = DeduceParamAttrs::new(body);
199+
deduce.visit_body(body);
200+
tracing::trace!(?deduce.usage);
176201

177-
let mut deduced_param_attrs: &[_] = tcx.arena.alloc_from_iter(
178-
deduce_read_only.read_only.into_iter().map(|read_only| DeducedParamAttrs { read_only }),
179-
);
202+
let mut deduced_param_attrs: &[_] = tcx
203+
.arena
204+
.alloc_from_iter(deduce.usage.into_iter().map(|usage| DeducedParamAttrs { usage }));
180205

181206
// Trailing parameters past the size of the `deduced_param_attrs` array are assumed to have the
182207
// default set of attributes, so we don't have to store them explicitly. Pop them off to save a

compiler/rustc_target/src/callconv/mod.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,14 @@ mod attr_impl {
113113
pub struct ArgAttribute(u8);
114114
bitflags::bitflags! {
115115
impl ArgAttribute: u8 {
116-
const NoAlias = 1 << 1;
117-
const CapturesAddress = 1 << 2;
118-
const NonNull = 1 << 3;
119-
const ReadOnly = 1 << 4;
120-
const InReg = 1 << 5;
121-
const NoUndef = 1 << 6;
122-
const CapturesReadOnly = 1 << 7;
116+
const CapturesNone = 0b111;
117+
const CapturesAddress = 0b110;
118+
const CapturesReadOnly = 0b100;
119+
const NoAlias = 1 << 3;
120+
const NonNull = 1 << 4;
121+
const ReadOnly = 1 << 5;
122+
const InReg = 1 << 6;
123+
const NoUndef = 1 << 7;
123124
}
124125
}
125126
rustc_data_structures::external_bitflags_debug! { ArgAttribute }

0 commit comments

Comments
 (0)