Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 5f4e4e9

Browse files
committed
Auto merge of rust-lang#138154 - tmiasko:readonly-non-freeze, r=<try>
Deduce readonly attribute for !Freeze arguments r? `@ghost`
2 parents 2b285cd + d5899d2 commit 5f4e4e9

File tree

5 files changed

+98
-49
lines changed

5 files changed

+98
-49
lines changed

compiler/rustc_middle/src/ty/context.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3300,6 +3300,8 @@ pub struct DeducedParamAttrs {
33003300
/// The parameter is marked immutable in the function and contains no `UnsafeCell` (i.e. its
33013301
/// type is freeze).
33023302
pub read_only: bool,
3303+
pub requires_freeze: bool,
3304+
pub requires_nop_drop: bool,
33033305
}
33043306

33053307
pub fn provide(providers: &mut Providers) {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use rustc_middle::mir::*;
2+
use rustc_middle::ty::TyCtxt;
3+
use rustc_session::config::OptLevel;
4+
5+
pub(super) struct CopyArgs;
6+
7+
impl<'tcx> crate::MirPass<'tcx> for CopyArgs {
8+
fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
9+
sess.opts.optimize != OptLevel::No && sess.opts.incremental.is_none()
10+
}
11+
12+
fn run_pass(&self, _: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
13+
for (_, block) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
14+
if let TerminatorKind::Call { ref mut args, .. } = block.terminator_mut().kind {
15+
for arg in args {
16+
if let Operand::Move(place) = arg.node {
17+
if place.is_indirect() {
18+
continue;
19+
}
20+
let Some(local) = place.as_local() else {
21+
continue
22+
};
23+
if 1 <= local.index() && local.index() <= body.arg_count {
24+
arg.node = Operand::Copy(place);
25+
}
26+
}
27+
}
28+
};
29+
}
30+
}
31+
32+
fn is_required(&self) -> bool {
33+
false
34+
}
35+
}

compiler/rustc_mir_transform/src/deduce_param_attrs.rs

Lines changed: 53 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,54 +8,65 @@
88
use rustc_hir::def_id::LocalDefId;
99
use rustc_index::bit_set::DenseBitSet;
1010
use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
11-
use rustc_middle::mir::{Body, Location, Operand, Place, RETURN_PLACE, Terminator, TerminatorKind};
11+
use rustc_middle::mir::{
12+
Body, Local, Location, Operand, Place, RETURN_PLACE, Terminator, TerminatorKind,
13+
};
1214
use rustc_middle::ty::{self, DeducedParamAttrs, Ty, TyCtxt};
1315
use rustc_session::config::OptLevel;
16+
use tracing::instrument;
1417

1518
/// A visitor that determines which arguments have been mutated. We can't use the mutability field
1619
/// on LocalDecl for this because it has no meaning post-optimization.
1720
struct DeduceReadOnly {
1821
/// Each bit is indexed by argument number, starting at zero (so 0 corresponds to local decl
1922
/// 1). The bit is true if the argument may have been mutated or false if we know it hasn't
2023
/// been up to the point we're at.
21-
mutable_args: DenseBitSet<usize>,
24+
read_only: DenseBitSet<usize>,
25+
requires_freeze: DenseBitSet<usize>,
26+
requires_nop_drop: DenseBitSet<usize>,
2227
}
2328

2429
impl DeduceReadOnly {
2530
/// Returns a new DeduceReadOnly instance.
2631
fn new(arg_count: usize) -> Self {
27-
Self { mutable_args: DenseBitSet::new_empty(arg_count) }
32+
Self {
33+
read_only: DenseBitSet::new_filled(arg_count),
34+
requires_freeze: DenseBitSet::new_empty(arg_count),
35+
requires_nop_drop: DenseBitSet::new_empty(arg_count),
36+
}
37+
}
38+
39+
fn arg_index(&self, local: Local) -> Option<usize> {
40+
if local == RETURN_PLACE || local.index() > self.read_only.domain_size() {
41+
None
42+
} else {
43+
Some(local.index() - 1)
44+
}
2845
}
2946
}
3047

3148
impl<'tcx> Visitor<'tcx> for DeduceReadOnly {
3249
fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, _location: Location) {
33-
// We're only interested in arguments.
34-
if place.local == RETURN_PLACE || place.local.index() > self.mutable_args.domain_size() {
50+
if place.is_indirect() {
3551
return;
3652
}
37-
38-
let mark_as_mutable = match context {
53+
// We're only interested in arguments.
54+
let Some(arg_index) = self.arg_index(place.local) else { return };
55+
match context {
3956
PlaceContext::MutatingUse(..) => {
4057
// This is a mutation, so mark it as such.
41-
true
58+
self.read_only.remove(arg_index);
4259
}
4360
PlaceContext::NonMutatingUse(NonMutatingUseContext::RawBorrow) => {
4461
// Whether mutating though a `&raw const` is allowed is still undecided, so we
45-
// disable any sketchy `readonly` optimizations for now. But we only need to do
46-
// this if the pointer would point into the argument. IOW: for indirect places,
47-
// like `&raw (*local).field`, this surely cannot mutate `local`.
48-
!place.is_indirect()
62+
// disable any sketchy `readonly` optimizations for now.
63+
self.read_only.remove(arg_index);
4964
}
50-
PlaceContext::NonMutatingUse(..) | PlaceContext::NonUse(..) => {
51-
// Not mutating, so it's fine.
52-
false
65+
PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) => {
66+
self.requires_freeze.insert(arg_index);
5367
}
68+
PlaceContext::NonMutatingUse(..) | PlaceContext::NonUse(..) => {}
5469
};
55-
56-
if mark_as_mutable {
57-
self.mutable_args.insert(place.local.index() - 1);
58-
}
5970
}
6071

6172
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
@@ -83,18 +94,23 @@ impl<'tcx> Visitor<'tcx> for DeduceReadOnly {
8394
if let TerminatorKind::Call { ref args, .. } = terminator.kind {
8495
for arg in args {
8596
if let Operand::Move(place) = arg.node {
86-
let local = place.local;
87-
if place.is_indirect()
88-
|| local == RETURN_PLACE
89-
|| local.index() > self.mutable_args.domain_size()
90-
{
97+
if place.is_indirect() {
9198
continue;
9299
}
93-
94-
self.mutable_args.insert(local.index() - 1);
100+
if let Some(arg_index) = self.arg_index(place.local) {
101+
self.read_only.remove(arg_index);
102+
}
95103
}
96104
}
97105
};
106+
if let TerminatorKind::Drop { place, .. } = terminator.kind {
107+
if let Some(local) = place.as_local()
108+
&& let Some(arg_index) = self.arg_index(local)
109+
{
110+
self.requires_nop_drop.insert(arg_index);
111+
return;
112+
}
113+
}
98114

99115
self.super_terminator(terminator, location);
100116
}
@@ -121,6 +137,7 @@ fn type_will_always_be_passed_directly(ty: Ty<'_>) -> bool {
121137
/// body of the function instead of just the signature. These can be useful for optimization
122138
/// purposes on a best-effort basis. We compute them here and store them into the crate metadata so
123139
/// dependent crates can use them.
140+
#[instrument(level = "debug", ret, skip(tcx))]
124141
pub(super) fn deduced_param_attrs<'tcx>(
125142
tcx: TyCtxt<'tcx>,
126143
def_id: LocalDefId,
@@ -158,31 +175,19 @@ pub(super) fn deduced_param_attrs<'tcx>(
158175

159176
// Grab the optimized MIR. Analyze it to determine which arguments have been mutated.
160177
let body: &Body<'tcx> = tcx.optimized_mir(def_id);
161-
let mut deduce_read_only = DeduceReadOnly::new(body.arg_count);
162-
deduce_read_only.visit_body(body);
178+
let mut deduce = DeduceReadOnly::new(body.arg_count);
179+
deduce.visit_body(body);
163180

164181
// Set the `readonly` attribute for every argument that we concluded is immutable and that
165182
// contains no UnsafeCells.
166-
//
167-
// FIXME: This is overly conservative around generic parameters: `is_freeze()` will always
168-
// return false for them. For a description of alternatives that could do a better job here,
169-
// see [1].
170-
//
171-
// [1]: https://github.com/rust-lang/rust/pull/103172#discussion_r999139997
172-
let typing_env = body.typing_env(tcx);
173-
let mut deduced_param_attrs = tcx.arena.alloc_from_iter(
174-
body.local_decls.iter().skip(1).take(body.arg_count).enumerate().map(
175-
|(arg_index, local_decl)| DeducedParamAttrs {
176-
read_only: !deduce_read_only.mutable_args.contains(arg_index)
177-
// We must normalize here to reveal opaques and normalize
178-
// their generic parameters, otherwise we'll see exponential
179-
// blow-up in compile times: #113372
180-
&& tcx
181-
.normalize_erasing_regions(typing_env, local_decl.ty)
182-
.is_freeze(tcx, typing_env),
183-
},
184-
),
185-
);
183+
let mut deduced_param_attrs = tcx.arena.alloc_from_iter((0..body.arg_count).map(|arg_index| {
184+
let read_only = deduce.read_only.contains(arg_index);
185+
DeducedParamAttrs {
186+
read_only,
187+
requires_freeze: read_only && deduce.requires_freeze.contains(arg_index),
188+
requires_nop_drop: read_only && deduce.requires_nop_drop.contains(arg_index),
189+
}
190+
}));
186191

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

compiler/rustc_mir_transform/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ declare_passes! {
127127
// This pass is public to allow external drivers to perform MIR cleanup
128128
pub mod cleanup_post_borrowck : CleanupPostBorrowck;
129129

130+
mod copy_args : CopyArgs;
130131
mod copy_prop : CopyProp;
131132
mod coroutine : StateTransform;
132133
mod coverage : InstrumentCoverage;
@@ -725,6 +726,7 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
725726
&large_enums::EnumSizeOpt { discrepancy: 128 },
726727
// Some cleanup necessary at least for LLVM and potentially other codegen backends.
727728
&add_call_guards::CriticalCallEdges,
729+
&copy_args::CopyArgs,
728730
// Cleanup for human readability, off by default.
729731
&prettify::ReorderBasicBlocks,
730732
&prettify::ReorderLocals,

compiler/rustc_ty_utils/src/abi.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,12 @@ fn fn_abi_adjust_for_abi<'tcx>(
712712
// we can't deduce any parameters for, so make sure the argument index is in
713713
// bounds.
714714
if let Some(deduced_param_attrs) = deduced_param_attrs.get(arg_idx) {
715-
if deduced_param_attrs.read_only {
715+
if deduced_param_attrs.read_only
716+
&& (!deduced_param_attrs.requires_freeze
717+
|| arg.layout.ty.is_freeze(tcx, cx.typing_env))
718+
&& (!deduced_param_attrs.requires_nop_drop
719+
|| !arg.layout.ty.needs_drop(tcx, cx.typing_env))
720+
{
716721
attrs.regular.insert(ArgAttribute::ReadOnly);
717722
debug!("added deduced read-only attribute");
718723
}

0 commit comments

Comments
 (0)