Skip to content

Commit eab5de9

Browse files
committed
Deduce captures(none) for a return place and parameters
1 parent f5e2df7 commit eab5de9

File tree

17 files changed

+327
-192
lines changed

17 files changed

+327
-192
lines changed

compiler/rustc_codegen_llvm/src/abi.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,17 @@ 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),
47+
];
48+
49+
// The order of attributes is significant. Only the first present is applied.
50+
const CAPTURES_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 3] = [
51+
(ArgAttribute::CapturesNone, llvm::AttributeKind::CapturesNone),
52+
(ArgAttribute::CapturesAddress, llvm::AttributeKind::CapturesAddress),
4853
(ArgAttribute::CapturesReadOnly, llvm::AttributeKind::CapturesReadOnly),
4954
];
5055

@@ -81,15 +86,18 @@ fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'
8186
}
8287
for (attr, llattr) in OPTIMIZATION_ATTRIBUTES {
8388
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-
}
9089
attrs.push(llattr.create_attr(cx.llcx));
9190
}
9291
}
92+
// captures(...) is only available since LLVM 21.
93+
if (21, 0, 0) <= llvm_util::get_version() {
94+
for (attr, llattr) in CAPTURES_ATTRIBUTES {
95+
if regular.contains(attr) {
96+
attrs.push(llattr.create_attr(cx.llcx));
97+
break;
98+
}
99+
}
100+
}
93101
} else if cx.tcx.sess.opts.unstable_opts.sanitizer.contains(SanitizerSet::MEMORY) {
94102
// If we're not optimising, *but* memory sanitizer is on, emit noundef, since it affects
95103
// 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: 35 additions & 20 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 return place or a parameter 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

@@ -28,22 +30,21 @@ bitflags::bitflags! {
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+
pub usage: UsageSummary,
3334
}
3435

35-
// By default, consider the parameters to be mutated.
3636
impl Default for DeducedParamAttrs {
37+
/// By default parameter is considered to be both mutated and captured.
3738
#[inline]
3839
fn default() -> DeducedParamAttrs {
39-
DeducedParamAttrs { read_only: DeducedReadOnlyParam::MUTATED }
40+
DeducedParamAttrs { usage: UsageSummary::MUTATE | UsageSummary::CAPTURE }
4041
}
4142
}
4243

4344
impl DeducedParamAttrs {
4445
#[inline]
4546
pub fn is_default(self) -> bool {
46-
self.read_only.contains(DeducedReadOnlyParam::MUTATED)
47+
self.usage.contains(DeducedParamAttrs::default().usage)
4748
}
4849

4950
pub fn read_only<'tcx>(
@@ -52,15 +53,29 @@ impl DeducedParamAttrs {
5253
typing_env: TypingEnv<'tcx>,
5354
ty: Ty<'tcx>,
5455
) -> 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) {
56+
// Only if all checks pass is this truly read-only.
57+
if self.usage.contains(UsageSummary::MUTATE) {
5858
return false;
5959
}
60-
if read_only.contains(DeducedReadOnlyParam::IF_NO_DROP) && ty.needs_drop(tcx, typing_env) {
60+
if self.usage.contains(UsageSummary::DROP) && ty.needs_drop(tcx, typing_env) {
6161
return false;
6262
}
63-
if read_only.contains(DeducedReadOnlyParam::IF_FREEZE) && !ty.is_freeze(tcx, typing_env) {
63+
if self.usage.contains(UsageSummary::SHARED_BORROW) && !ty.is_freeze(tcx, typing_env) {
64+
return false;
65+
}
66+
true
67+
}
68+
69+
pub fn captures_none<'tcx>(
70+
&self,
71+
tcx: TyCtxt<'tcx>,
72+
typing_env: TypingEnv<'tcx>,
73+
ty: Ty<'tcx>,
74+
) -> bool {
75+
if self.usage.contains(UsageSummary::CAPTURE) {
76+
return false;
77+
}
78+
if self.usage.contains(UsageSummary::DROP) && ty.needs_drop(tcx, typing_env) {
6479
return false;
6580
}
6681
true

compiler/rustc_mir_transform/src/deduce_param_attrs.rs

Lines changed: 69 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -11,98 +11,106 @@
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<usize, 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[0] |= UsageSummary::MUTATE;
37+
this
3338
}
3439

35-
/// Returns whether the given local is a parameter and its index.
40+
/// Returns whether a local is the return place or an argument and returns its index.
3641
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 }
42+
let i = local.as_usize();
43+
if i < self.usage.len() { Some(i) } else { None }
4044
}
4145
}
4246

43-
impl<'tcx> Visitor<'tcx> for DeduceReadOnly {
47+
impl<'tcx> Visitor<'tcx> for DeduceParamAttrs {
4448
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 };
49+
// We're only interested in the return place or an argument.
50+
let Some(i) = self.as_param(place.local) else { return };
4751

4852
match context {
49-
// Not mutating, so it's fine.
53+
// Not actually using the local.
5054
PlaceContext::NonUse(..) => {}
51-
// Dereference is not a mutation.
55+
// Neither mutated nor captured.
5256
_ if place.is_indirect_first_projection() => {}
5357
// This is a `Drop`. It could disappear at monomorphization, so mark it specially.
5458
PlaceContext::MutatingUse(MutatingUseContext::Drop)
5559
// Projection changes the place's type, so `needs_drop(local.ty)` is not
5660
// `needs_drop(place.ty)`.
5761
if place.projection.is_empty() => {
58-
self.read_only[param_index] |= DeducedReadOnlyParam::IF_NO_DROP;
62+
self.usage[i] |= UsageSummary::DROP;
63+
}
64+
PlaceContext::MutatingUse(
65+
MutatingUseContext::Call
66+
| MutatingUseContext::Yield
67+
| MutatingUseContext::Drop
68+
| MutatingUseContext::Borrow
69+
| MutatingUseContext::RawBorrow) => {
70+
self.usage[i] |= UsageSummary::MUTATE;
71+
self.usage[i] |= UsageSummary::CAPTURE;
72+
}
73+
PlaceContext::MutatingUse(
74+
MutatingUseContext::Store
75+
| MutatingUseContext::SetDiscriminant
76+
| MutatingUseContext::AsmOutput
77+
| MutatingUseContext::Projection
78+
| MutatingUseContext::Retag) => {
79+
self.usage[i] |= UsageSummary::MUTATE;
5980
}
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.
6481
| PlaceContext::NonMutatingUse(NonMutatingUseContext::RawBorrow) => {
65-
self.read_only[param_index] |= DeducedReadOnlyParam::MUTATED;
82+
// Whether mutating though a `&raw const` is allowed is still undecided, so we
83+
// disable any sketchy `readonly` optimizations for now.
84+
self.usage[i] |= UsageSummary::MUTATE;
85+
self.usage[i] |= UsageSummary::CAPTURE;
6686
}
67-
// Not mutating if the parameter is `Freeze`.
6887
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) => {
69-
self.read_only[param_index] |= DeducedReadOnlyParam::IF_FREEZE;
88+
// Not mutating if the parameter is `Freeze`.
89+
self.usage[i] |= UsageSummary::SHARED_BORROW;
90+
self.usage[i] |= UsageSummary::CAPTURE;
7091
}
7192
// Not mutating, so it's fine.
72-
PlaceContext::NonMutatingUse(..) => {}
93+
PlaceContext::NonMutatingUse(
94+
NonMutatingUseContext::Inspect
95+
| NonMutatingUseContext::Copy
96+
| NonMutatingUseContext::Move
97+
| NonMutatingUseContext::FakeBorrow
98+
| NonMutatingUseContext::PlaceMention
99+
| NonMutatingUseContext::Projection) => {}
73100
}
74101
}
75102

76103
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
77-
// OK, this is subtle. Suppose that we're trying to deduce whether `x` in `f` is read-only
78-
// and we have the following:
79-
//
80-
// fn f(x: BigStruct) { g(x) }
81-
// fn g(mut y: BigStruct) { y.foo = 1 }
82-
//
83-
// If, at the generated MIR level, `f` turned into something like:
84-
//
85-
// fn f(_1: BigStruct) -> () {
86-
// let mut _0: ();
87-
// bb0: {
88-
// _0 = g(move _1) -> bb1;
89-
// }
90-
// ...
91-
// }
92-
//
93-
// then it would be incorrect to mark `x` (i.e. `_1`) as `readonly`, because `g`'s write to
94-
// its copy of the indirect parameter would actually be a write directly to the pointer that
95-
// `f` passes. Note that function arguments are the only situation in which this problem can
96-
// arise: every other use of `move` in MIR doesn't actually write to the value it moves
97-
// from.
98104
if let TerminatorKind::Call { ref args, .. } = terminator.kind {
99105
for arg in args {
100106
if let Operand::Move(place) = arg.node
101-
// We're only interested in arguments.
102-
&& let Some(param_index) = self.as_param(place.local)
103107
&& !place.is_indirect_first_projection()
108+
&& let Some(i) = self.as_param(place.local)
104109
{
105-
self.read_only[param_index] |= DeducedReadOnlyParam::MUTATED;
110+
// Moved arguments might be passed in-place, which allows callee to mutate them
111+
// or capture their address.
112+
self.usage[i] |= UsageSummary::MUTATE;
113+
self.usage[i] |= UsageSummary::CAPTURE;
106114
}
107115
}
108116
};
@@ -154,10 +162,9 @@ pub(super) fn deduced_param_attrs<'tcx>(
154162
if matches!(fn_ty.kind(), ty::FnDef(..))
155163
&& fn_ty
156164
.fn_sig(tcx)
157-
.inputs()
165+
.inputs_and_output()
158166
.skip_binder()
159167
.iter()
160-
.cloned()
161168
.all(type_will_always_be_passed_directly)
162169
{
163170
return &[];
@@ -170,13 +177,13 @@ pub(super) fn deduced_param_attrs<'tcx>(
170177

171178
// Grab the optimized MIR. Analyze it to determine which arguments have been mutated.
172179
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);
180+
let mut deduce = DeduceParamAttrs::new(body);
181+
deduce.visit_body(body);
182+
tracing::trace!(?deduce.usage);
176183

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-
);
184+
let mut deduced_param_attrs: &[_] = tcx
185+
.arena
186+
.alloc_from_iter(deduce.usage.into_iter().map(|usage| DeducedParamAttrs { usage }));
180187

181188
// Trailing parameters past the size of the `deduced_param_attrs` array are assumed to have the
182189
// 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)