Skip to content

Commit 470704e

Browse files
Auto merge of #147890 - tmiasko:deduce-captures-none, r=<try>
deduced_param_attrs: deduce captures(none)
2 parents c6efb90 + 5d4db8a commit 470704e

File tree

16 files changed

+169
-122
lines changed

16 files changed

+169
-122
lines changed

compiler/rustc_codegen_llvm/src/abi.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,16 @@ 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+
const CAPTURES_ATTRIBUTES: [(ArgAttribute, llvm::AttributeKind); 3] = [
50+
(ArgAttribute::CapturesNone, llvm::AttributeKind::CapturesNone),
51+
(ArgAttribute::CapturesAddress, llvm::AttributeKind::CapturesAddress),
4852
(ArgAttribute::CapturesReadOnly, llvm::AttributeKind::CapturesReadOnly),
4953
];
5054

@@ -81,15 +85,18 @@ fn get_attrs<'ll>(this: &ArgAttributes, cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'
8185
}
8286
for (attr, llattr) in OPTIMIZATION_ATTRIBUTES {
8387
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-
}
9088
attrs.push(llattr.create_attr(cx.llcx));
9189
}
9290
}
91+
// captures(...) is only available since LLVM 21.
92+
if (21, 0, 0) <= llvm_util::get_version() {
93+
for (attr, llattr) in CAPTURES_ATTRIBUTES {
94+
if regular.contains(attr) {
95+
attrs.push(llattr.create_attr(cx.llcx));
96+
break;
97+
}
98+
}
99+
}
93100
} else if cx.tcx.sess.opts.unstable_opts.sanitizer.contains(SanitizerSet::MEMORY) {
94101
// If we're not optimising, *but* memory sanitizer is on, emit noundef, since it affects
95102
// 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: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@ use crate::ty::{Ty, TyCtxt, TypingEnv};
55
/// Flags that dictate how a parameter is mutated. If the flags are empty, the param is
66
/// read-only. If non-empty, it is read-only with conditions.
77
#[derive(Clone, Copy, PartialEq, Debug, Decodable, Encodable, HashStable)]
8-
pub struct DeducedReadOnlyParam(u8);
8+
pub struct DeducedParamFlags(u8);
99

1010
bitflags::bitflags! {
11-
impl DeducedReadOnlyParam: u8 {
11+
impl DeducedParamFlags: u8 {
1212
/// This parameter is dropped. It is read-only if `!needs_drop`.
13-
const IF_NO_DROP = 1 << 0;
13+
const IF_NO_DROP = 1 << 0;
1414
/// This parameter is borrowed. It is read-only if `Freeze`.
1515
const IF_FREEZE = 1 << 1;
1616
/// This parameter is mutated. It is never read-only.
1717
const MUTATED = 1 << 2;
18+
/// This parameter is captured.
19+
const CAPTURED = 1 << 3;
1820
}
1921
}
2022

@@ -29,21 +31,21 @@ bitflags::bitflags! {
2931
#[derive(Clone, Copy, PartialEq, Debug, Decodable, Encodable, HashStable)]
3032
pub struct DeducedParamAttrs {
3133
/// The parameter is marked immutable in the function.
32-
pub read_only: DeducedReadOnlyParam,
34+
pub flags: DeducedParamFlags,
3335
}
3436

35-
// By default, consider the parameters to be mutated.
3637
impl Default for DeducedParamAttrs {
38+
/// By default parameter is considered to be mutated and captured.
3739
#[inline]
3840
fn default() -> DeducedParamAttrs {
39-
DeducedParamAttrs { read_only: DeducedReadOnlyParam::MUTATED }
41+
DeducedParamAttrs { flags: DeducedParamFlags::MUTATED | DeducedParamFlags::CAPTURED }
4042
}
4143
}
4244

4345
impl DeducedParamAttrs {
4446
#[inline]
4547
pub fn is_default(self) -> bool {
46-
self.read_only.contains(DeducedReadOnlyParam::MUTATED)
48+
self.flags.contains(DeducedParamAttrs::default().flags)
4749
}
4850

4951
pub fn read_only<'tcx>(
@@ -52,14 +54,30 @@ impl DeducedParamAttrs {
5254
typing_env: TypingEnv<'tcx>,
5355
ty: Ty<'tcx>,
5456
) -> bool {
55-
let read_only = self.read_only;
56-
if read_only.contains(DeducedReadOnlyParam::MUTATED) {
57+
let flags = self.flags;
58+
if flags.contains(DeducedParamFlags::MUTATED) {
5759
return false;
5860
}
59-
if read_only.contains(DeducedReadOnlyParam::IF_NO_DROP) && ty.needs_drop(tcx, typing_env) {
61+
if flags.contains(DeducedParamFlags::IF_NO_DROP) && ty.needs_drop(tcx, typing_env) {
6062
return false;
6163
}
62-
if read_only.contains(DeducedReadOnlyParam::IF_FREEZE) && !ty.is_freeze(tcx, typing_env) {
64+
if flags.contains(DeducedParamFlags::IF_FREEZE) && !ty.is_freeze(tcx, typing_env) {
65+
return false;
66+
}
67+
true
68+
}
69+
70+
pub fn captures_none<'tcx>(
71+
&self,
72+
tcx: TyCtxt<'tcx>,
73+
typing_env: TypingEnv<'tcx>,
74+
ty: Ty<'tcx>,
75+
) -> bool {
76+
let flags = self.flags;
77+
if flags.contains(DeducedParamFlags::CAPTURED) {
78+
return false;
79+
}
80+
if flags.contains(DeducedParamFlags::IF_NO_DROP) && ty.needs_drop(tcx, typing_env) {
6381
return false;
6482
}
6583
true

compiler/rustc_mir_transform/src/deduce_param_attrs.rs

Lines changed: 63 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,98 +7,105 @@
77
88
use rustc_hir::def_id::LocalDefId;
99
use rustc_index::IndexVec;
10-
use rustc_middle::middle::deduced_param_attrs::{DeducedParamAttrs, DeducedReadOnlyParam};
10+
use rustc_middle::middle::deduced_param_attrs::{DeducedParamAttrs, DeducedParamFlags};
1111
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
1212
use rustc_middle::mir::*;
1313
use rustc_middle::ty::{self, Ty, TyCtxt};
1414
use rustc_session::config::OptLevel;
1515

1616
/// A visitor that determines which arguments have been mutated. We can't use the mutability field
1717
/// on LocalDecl for this because it has no meaning post-optimization.
18-
struct DeduceReadOnly {
18+
struct DeduceParamAttrs {
1919
/// Each bit is indexed by argument number, starting at zero (so 0 corresponds to local decl
2020
/// 1). The bit is false if the argument may have been mutated or true if we know it hasn't
2121
/// been up to the point we're at.
22-
read_only: IndexVec<usize, DeducedReadOnlyParam>,
22+
flags: IndexVec<usize, DeducedParamFlags>,
2323
}
2424

25-
impl DeduceReadOnly {
26-
/// Returns a new DeduceReadOnly instance.
27-
fn new(arg_count: usize) -> Self {
28-
Self { read_only: IndexVec::from_elem_n(DeducedReadOnlyParam::empty(), arg_count) }
25+
impl DeduceParamAttrs {
26+
/// Returns a new DeduceParamAttrs instance.
27+
fn new(body: &Body<'_>) -> Self {
28+
let mut this =
29+
Self { flags: IndexVec::from_elem_n(DeducedParamFlags::empty(), body.arg_count + 1) };
30+
// Mark the return place as mutated.
31+
this.flags[0] |= DeducedParamFlags::MUTATED;
32+
this
2933
}
3034

31-
/// Returns whether the given local is a parameter and its index.
35+
/// Returns whether the given local is a return place parameter and its index.
3236
fn as_param(&self, local: Local) -> Option<usize> {
33-
// Locals and parameters are shifted by `RETURN_PLACE`.
34-
let param_index = local.as_usize().checked_sub(1)?;
35-
if param_index < self.read_only.len() { Some(param_index) } else { None }
37+
let i = local.as_usize();
38+
if i < self.flags.len() { Some(i) } else { None }
3639
}
3740
}
3841

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

4447
match context {
45-
// Not mutating, so it's fine.
48+
// Not actually using the local.
4649
PlaceContext::NonUse(..) => {}
47-
// Dereference is not a mutation.
50+
// Neither mutated nor captured.
4851
_ if place.is_indirect_first_projection() => {}
4952
// This is a `Drop`. It could disappear at monomorphization, so mark it specially.
5053
PlaceContext::MutatingUse(MutatingUseContext::Drop)
5154
// Projection changes the place's type, so `needs_drop(local.ty)` is not
5255
// `needs_drop(place.ty)`.
5356
if place.projection.is_empty() => {
54-
self.read_only[param_index] |= DeducedReadOnlyParam::IF_NO_DROP;
57+
self.flags[i] |= DeducedParamFlags::IF_NO_DROP;
58+
}
59+
PlaceContext::MutatingUse(
60+
MutatingUseContext::Call
61+
| MutatingUseContext::Yield
62+
| MutatingUseContext::Drop
63+
| MutatingUseContext::Borrow
64+
| MutatingUseContext::RawBorrow) => {
65+
self.flags[i] |= DeducedParamFlags::MUTATED;
66+
self.flags[i] |= DeducedParamFlags::CAPTURED;
67+
}
68+
PlaceContext::MutatingUse(
69+
MutatingUseContext::Store
70+
| MutatingUseContext::SetDiscriminant
71+
| MutatingUseContext::AsmOutput
72+
| MutatingUseContext::Projection
73+
| MutatingUseContext::Retag) => {
74+
self.flags[i] |= DeducedParamFlags::MUTATED;
5575
}
56-
// This is a mutation, so mark it as such.
57-
PlaceContext::MutatingUse(..)
58-
// Whether mutating though a `&raw const` is allowed is still undecided, so we
59-
// disable any sketchy `readonly` optimizations for now.
6076
| PlaceContext::NonMutatingUse(NonMutatingUseContext::RawBorrow) => {
61-
self.read_only[param_index] |= DeducedReadOnlyParam::MUTATED;
77+
// Whether mutating though a `&raw const` is allowed is still undecided, so we
78+
// disable any sketchy `readonly` optimizations for now.
79+
self.flags[i] |= DeducedParamFlags::MUTATED;
80+
self.flags[i] |= DeducedParamFlags::CAPTURED;
6281
}
63-
// Not mutating if the parameter is `Freeze`.
6482
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) => {
65-
self.read_only[param_index] |= DeducedReadOnlyParam::IF_FREEZE;
83+
// Not mutating if the parameter is `Freeze`.
84+
self.flags[i] |= DeducedParamFlags::IF_FREEZE;
85+
self.flags[i] |= DeducedParamFlags::CAPTURED;
6686
}
6787
// Not mutating, so it's fine.
68-
PlaceContext::NonMutatingUse(..) => {}
88+
PlaceContext::NonMutatingUse(
89+
NonMutatingUseContext::Inspect
90+
| NonMutatingUseContext::Copy
91+
| NonMutatingUseContext::Move
92+
| NonMutatingUseContext::FakeBorrow
93+
| NonMutatingUseContext::PlaceMention
94+
| NonMutatingUseContext::Projection) => {}
6995
}
7096
}
7197

7298
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
73-
// OK, this is subtle. Suppose that we're trying to deduce whether `x` in `f` is read-only
74-
// and we have the following:
75-
//
76-
// fn f(x: BigStruct) { g(x) }
77-
// fn g(mut y: BigStruct) { y.foo = 1 }
78-
//
79-
// If, at the generated MIR level, `f` turned into something like:
80-
//
81-
// fn f(_1: BigStruct) -> () {
82-
// let mut _0: ();
83-
// bb0: {
84-
// _0 = g(move _1) -> bb1;
85-
// }
86-
// ...
87-
// }
88-
//
89-
// then it would be incorrect to mark `x` (i.e. `_1`) as `readonly`, because `g`'s write to
90-
// its copy of the indirect parameter would actually be a write directly to the pointer that
91-
// `f` passes. Note that function arguments are the only situation in which this problem can
92-
// arise: every other use of `move` in MIR doesn't actually write to the value it moves
93-
// from.
9499
if let TerminatorKind::Call { ref args, .. } = terminator.kind {
95100
for arg in args {
96101
if let Operand::Move(place) = arg.node
97-
// We're only interested in arguments.
98-
&& let Some(param_index) = self.as_param(place.local)
99102
&& !place.is_indirect_first_projection()
103+
&& let Some(i) = self.as_param(place.local)
100104
{
101-
self.read_only[param_index] |= DeducedReadOnlyParam::MUTATED;
105+
// Move arguments might be passed in-place allowing callee to both mutate the
106+
// local and capture its address.
107+
self.flags[i] |= DeducedParamFlags::MUTATED;
108+
self.flags[i] |= DeducedParamFlags::CAPTURED;
102109
}
103110
}
104111
};
@@ -150,10 +157,9 @@ pub(super) fn deduced_param_attrs<'tcx>(
150157
if matches!(fn_ty.kind(), ty::FnDef(..))
151158
&& fn_ty
152159
.fn_sig(tcx)
153-
.inputs()
160+
.inputs_and_output()
154161
.skip_binder()
155162
.iter()
156-
.cloned()
157163
.all(type_will_always_be_passed_directly)
158164
{
159165
return &[];
@@ -166,13 +172,13 @@ pub(super) fn deduced_param_attrs<'tcx>(
166172

167173
// Grab the optimized MIR. Analyze it to determine which arguments have been mutated.
168174
let body: &Body<'tcx> = tcx.optimized_mir(def_id);
169-
let mut deduce_read_only = DeduceReadOnly::new(body.arg_count);
170-
deduce_read_only.visit_body(body);
171-
tracing::trace!(?deduce_read_only.read_only);
175+
let mut deduce = DeduceParamAttrs::new(body);
176+
deduce.visit_body(body);
177+
tracing::trace!(?deduce.flags);
172178

173-
let mut deduced_param_attrs: &[_] = tcx.arena.alloc_from_iter(
174-
deduce_read_only.read_only.into_iter().map(|read_only| DeducedParamAttrs { read_only }),
175-
);
179+
let mut deduced_param_attrs: &[_] = tcx
180+
.arena
181+
.alloc_from_iter(deduce.flags.into_iter().map(|flags| DeducedParamAttrs { flags }));
176182

177183
// Trailing parameters past the size of the `deduced_param_attrs` array are assumed to have the
178184
// 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: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,15 @@ 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 CapturesAll = 0b000;
120+
const NoAlias = 1 << 3;
121+
const NonNull = 1 << 4;
122+
const ReadOnly = 1 << 5;
123+
const InReg = 1 << 6;
124+
const NoUndef = 1 << 7;
123125
}
124126
}
125127
rustc_data_structures::external_bitflags_debug! { ArgAttribute }

0 commit comments

Comments
 (0)