Skip to content

Commit e109aa3

Browse files
authored
Rollup merge of #83519 - oli-obk:assign_shrink_your_normal_code, r=pnkfelix
Implement a lint that highlights all moves larger than a configured limit Tracking issue: #83518 [MCP 420](rust-lang/compiler-team#420) still ~blazing~ in progress r? ```@pnkfelix``` The main open issue I see with this minimal impl of the feature is that the lint is immediately "stable" (so it can be named on stable), even if it is never executed on stable. I don't think we have the concept of unstable lint names or hiding lint names without an active feature gate, so that would be a bigger change.
2 parents e11a9fa + 85b1c67 commit e109aa3

File tree

13 files changed

+218
-21
lines changed

13 files changed

+218
-21
lines changed

compiler/rustc_feature/src/active.rs

+3
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,9 @@ declare_features! (
633633
/// Allows associated types in inherent impls.
634634
(active, inherent_associated_types, "1.52.0", Some(8995), None),
635635

636+
// Allows setting the threshold for the `large_assignments` lint.
637+
(active, large_assignments, "1.52.0", Some(83518), None),
638+
636639
/// Allows `extern "C-unwind" fn` to enable unwinding across ABI boundaries.
637640
(active, c_unwind, "1.52.0", Some(74990), None),
638641

compiler/rustc_feature/src/builtin_attrs.rs

+4
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
241241
const_eval_limit, CrateLevel, template!(NameValueStr: "N"), const_eval_limit,
242242
experimental!(const_eval_limit)
243243
),
244+
gated!(
245+
move_size_limit, CrateLevel, template!(NameValueStr: "N"), large_assignments,
246+
experimental!(move_size_limit)
247+
),
244248

245249
// Entry point:
246250
ungated!(main, Normal, template!(Word)),

compiler/rustc_lint_defs/src/builtin.rs

+34
Original file line numberDiff line numberDiff line change
@@ -2877,6 +2877,39 @@ declare_lint! {
28772877
};
28782878
}
28792879

2880+
declare_lint! {
2881+
/// The `large_assignments` lint detects when objects of large
2882+
/// types are being moved around.
2883+
///
2884+
/// ### Example
2885+
///
2886+
/// ```rust,ignore (can crash on some platforms)
2887+
/// let x = [0; 50000];
2888+
/// let y = x;
2889+
/// ```
2890+
///
2891+
/// produces:
2892+
///
2893+
/// ```text
2894+
/// warning: moving a large value
2895+
/// --> $DIR/move-large.rs:1:3
2896+
/// let y = x;
2897+
/// - Copied large value here
2898+
/// ```
2899+
///
2900+
/// ### Explanation
2901+
///
2902+
/// When using a large type in a plain assignment or in a function
2903+
/// argument, idiomatic code can be inefficient.
2904+
/// Ideally appropriate optimizations would resolve this, but such
2905+
/// optimizations are only done in a best-effort manner.
2906+
/// This lint will trigger on all sites of large moves and thus allow the
2907+
/// user to resolve them in code.
2908+
pub LARGE_ASSIGNMENTS,
2909+
Warn,
2910+
"detects large moves or copies",
2911+
}
2912+
28802913
declare_lint_pass! {
28812914
/// Does nothing as a lint pass, but registers some `Lint`s
28822915
/// that are used by other parts of the compiler.
@@ -2962,6 +2995,7 @@ declare_lint_pass! {
29622995
LEGACY_DERIVE_HELPERS,
29632996
PROC_MACRO_BACK_COMPAT,
29642997
OR_PATTERNS_BACK_COMPAT,
2998+
LARGE_ASSIGNMENTS,
29652999
]
29663000
}
29673001

compiler/rustc_middle/src/middle/limits.rs

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
//! Registering limits, recursion_limit, type_length_limit and const_eval_limit
1+
//! Registering limits:
2+
//! * recursion_limit,
3+
//! * move_size_limit,
4+
//! * type_length_limit, and
5+
//! * const_eval_limit
26
//!
37
//! There are various parts of the compiler that must impose arbitrary limits
48
//! on how deeply they recurse to prevent stack overflow. Users can override
@@ -8,21 +12,22 @@
812
use crate::bug;
913
use rustc_ast as ast;
1014
use rustc_data_structures::sync::OnceCell;
11-
use rustc_session::{Limit, Session};
15+
use rustc_session::Session;
1216
use rustc_span::symbol::{sym, Symbol};
1317

1418
use std::num::IntErrorKind;
1519

1620
pub fn update_limits(sess: &Session, krate: &ast::Crate) {
1721
update_limit(sess, krate, &sess.recursion_limit, sym::recursion_limit, 128);
22+
update_limit(sess, krate, &sess.move_size_limit, sym::move_size_limit, 0);
1823
update_limit(sess, krate, &sess.type_length_limit, sym::type_length_limit, 1048576);
1924
update_limit(sess, krate, &sess.const_eval_limit, sym::const_eval_limit, 1_000_000);
2025
}
2126

2227
fn update_limit(
2328
sess: &Session,
2429
krate: &ast::Crate,
25-
limit: &OnceCell<Limit>,
30+
limit: &OnceCell<impl From<usize> + std::fmt::Debug>,
2631
name: Symbol,
2732
default: usize,
2833
) {
@@ -34,7 +39,7 @@ fn update_limit(
3439
if let Some(s) = attr.value_str() {
3540
match s.as_str().parse() {
3641
Ok(n) => {
37-
limit.set(Limit::new(n)).unwrap();
42+
limit.set(From::from(n)).unwrap();
3843
return;
3944
}
4045
Err(e) => {
@@ -63,5 +68,5 @@ fn update_limit(
6368
}
6469
}
6570
}
66-
limit.set(Limit::new(default)).unwrap();
71+
limit.set(From::from(default)).unwrap();
6772
}

compiler/rustc_middle/src/mir/mod.rs

+24-1
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ use crate::ty::print::{FmtPrinter, Printer};
1212
use crate::ty::subst::{Subst, SubstsRef};
1313
use crate::ty::{self, List, Ty, TyCtxt};
1414
use crate::ty::{AdtDef, InstanceDef, Region, ScalarInt, UserTypeAnnotationIndex};
15-
use rustc_hir as hir;
1615
use rustc_hir::def::{CtorKind, Namespace};
1716
use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX};
1817
use rustc_hir::{self, GeneratorKind};
18+
use rustc_hir::{self as hir, HirId};
1919
use rustc_target::abi::{Size, VariantIdx};
2020

2121
use polonius_engine::Atom;
@@ -1948,6 +1948,29 @@ rustc_index::newtype_index! {
19481948
}
19491949
}
19501950

1951+
impl SourceScope {
1952+
/// Finds the original HirId this MIR item came from.
1953+
/// This is necessary after MIR optimizations, as otherwise we get a HirId
1954+
/// from the function that was inlined instead of the function call site.
1955+
pub fn lint_root(
1956+
self,
1957+
source_scopes: &IndexVec<SourceScope, SourceScopeData<'tcx>>,
1958+
) -> Option<HirId> {
1959+
let mut data = &source_scopes[self];
1960+
// FIXME(oli-obk): we should be able to just walk the `inlined_parent_scope`, but it
1961+
// does not work as I thought it would. Needs more investigation and documentation.
1962+
while data.inlined.is_some() {
1963+
trace!(?data);
1964+
data = &source_scopes[data.parent_scope.unwrap()];
1965+
}
1966+
trace!(?data);
1967+
match &data.local_data {
1968+
ClearCrossCrate::Set(data) => Some(data.lint_root),
1969+
ClearCrossCrate::Clear => None,
1970+
}
1971+
}
1972+
}
1973+
19511974
#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
19521975
pub struct SourceScopeData<'tcx> {
19531976
pub span: Span,

compiler/rustc_mir/src/monomorphize/collector.rs

+42
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,9 @@ use rustc_middle::ty::subst::{GenericArgKind, InternalSubsts};
198198
use rustc_middle::ty::{self, GenericParamDefKind, Instance, Ty, TyCtxt, TypeFoldable};
199199
use rustc_middle::{middle::codegen_fn_attrs::CodegenFnAttrFlags, mir::visit::TyContext};
200200
use rustc_session::config::EntryFnType;
201+
use rustc_session::lint::builtin::LARGE_ASSIGNMENTS;
201202
use rustc_span::source_map::{dummy_spanned, respan, Span, Spanned, DUMMY_SP};
203+
use rustc_target::abi::Size;
202204
use smallvec::SmallVec;
203205
use std::iter;
204206
use std::ops::Range;
@@ -753,6 +755,46 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirNeighborCollector<'a, 'tcx> {
753755
self.super_terminator(terminator, location);
754756
}
755757

758+
fn visit_operand(&mut self, operand: &mir::Operand<'tcx>, location: Location) {
759+
self.super_operand(operand, location);
760+
let limit = self.tcx.sess.move_size_limit();
761+
if limit == 0 {
762+
return;
763+
}
764+
let limit = Size::from_bytes(limit);
765+
let ty = operand.ty(self.body, self.tcx);
766+
let ty = self.monomorphize(ty);
767+
let layout = self.tcx.layout_of(ty::ParamEnv::reveal_all().and(ty));
768+
if let Ok(layout) = layout {
769+
if layout.size > limit {
770+
debug!(?layout);
771+
let source_info = self.body.source_info(location);
772+
debug!(?source_info);
773+
let lint_root = source_info.scope.lint_root(&self.body.source_scopes);
774+
debug!(?lint_root);
775+
let lint_root = match lint_root {
776+
Some(lint_root) => lint_root,
777+
// This happens when the issue is in a function from a foreign crate that
778+
// we monomorphized in the current crate. We can't get a `HirId` for things
779+
// in other crates.
780+
// FIXME: Find out where to report the lint on. Maybe simply crate-level lint root
781+
// but correct span? This would make the lint at least accept crate-level lint attributes.
782+
None => return,
783+
};
784+
self.tcx.struct_span_lint_hir(
785+
LARGE_ASSIGNMENTS,
786+
lint_root,
787+
source_info.span,
788+
|lint| {
789+
let mut err = lint.build(&format!("moving {} bytes", layout.size.bytes()));
790+
err.span_label(source_info.span, "value moved from here");
791+
err.emit()
792+
},
793+
);
794+
}
795+
}
796+
}
797+
756798
fn visit_local(
757799
&mut self,
758800
_place_local: &Local,

compiler/rustc_mir/src/transform/const_prop.rs

+4-15
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ use rustc_middle::mir::visit::{
1313
MutVisitor, MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor,
1414
};
1515
use rustc_middle::mir::{
16-
AssertKind, BasicBlock, BinOp, Body, ClearCrossCrate, Constant, ConstantKind, Local, LocalDecl,
17-
LocalKind, Location, Operand, Place, Rvalue, SourceInfo, SourceScope, SourceScopeData,
18-
Statement, StatementKind, Terminator, TerminatorKind, UnOp, RETURN_PLACE,
16+
AssertKind, BasicBlock, BinOp, Body, Constant, ConstantKind, Local, LocalDecl, LocalKind,
17+
Location, Operand, Place, Rvalue, SourceInfo, SourceScope, SourceScopeData, Statement,
18+
StatementKind, Terminator, TerminatorKind, UnOp, RETURN_PLACE,
1919
};
2020
use rustc_middle::ty::layout::{HasTyCtxt, LayoutError, TyAndLayout};
2121
use rustc_middle::ty::subst::{InternalSubsts, Subst};
@@ -440,18 +440,7 @@ impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
440440
}
441441

442442
fn lint_root(&self, source_info: SourceInfo) -> Option<HirId> {
443-
let mut data = &self.source_scopes[source_info.scope];
444-
// FIXME(oli-obk): we should be able to just walk the `inlined_parent_scope`, but it
445-
// does not work as I thought it would. Needs more investigation and documentation.
446-
while data.inlined.is_some() {
447-
trace!(?data);
448-
data = &self.source_scopes[data.parent_scope.unwrap()];
449-
}
450-
trace!(?data);
451-
match &data.local_data {
452-
ClearCrossCrate::Set(data) => Some(data.lint_root),
453-
ClearCrossCrate::Clear => None,
454-
}
443+
source_info.scope.lint_root(&self.source_scopes)
455444
}
456445

457446
fn use_ecx<F, T>(&mut self, f: F) -> Option<T>

compiler/rustc_session/src/session.rs

+16
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ impl Limit {
8383
}
8484
}
8585

86+
impl From<usize> for Limit {
87+
fn from(value: usize) -> Self {
88+
Self::new(value)
89+
}
90+
}
91+
8692
impl fmt::Display for Limit {
8793
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8894
write!(f, "{}", self.0)
@@ -143,6 +149,10 @@ pub struct Session {
143149
/// operations such as auto-dereference and monomorphization.
144150
pub recursion_limit: OnceCell<Limit>,
145151

152+
/// The size at which the `large_assignments` lint starts
153+
/// being emitted.
154+
pub move_size_limit: OnceCell<usize>,
155+
146156
/// The maximum length of types during monomorphization.
147157
pub type_length_limit: OnceCell<Limit>,
148158

@@ -352,6 +362,11 @@ impl Session {
352362
self.recursion_limit.get().copied().unwrap()
353363
}
354364

365+
#[inline]
366+
pub fn move_size_limit(&self) -> usize {
367+
self.move_size_limit.get().copied().unwrap()
368+
}
369+
355370
#[inline]
356371
pub fn type_length_limit(&self) -> Limit {
357372
self.type_length_limit.get().copied().unwrap()
@@ -1414,6 +1429,7 @@ pub fn build_session(
14141429
features: OnceCell::new(),
14151430
lint_store: OnceCell::new(),
14161431
recursion_limit: OnceCell::new(),
1432+
move_size_limit: OnceCell::new(),
14171433
type_length_limit: OnceCell::new(),
14181434
const_eval_limit: OnceCell::new(),
14191435
incr_comp_session: OneThread::new(RefCell::new(IncrCompSession::NotInitialized)),

compiler/rustc_span/src/symbol.rs

+2
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,7 @@ symbols! {
669669
label_break_value,
670670
lang,
671671
lang_items,
672+
large_assignments,
672673
lateout,
673674
lazy_normalization_consts,
674675
le,
@@ -749,6 +750,7 @@ symbols! {
749750
more_struct_aliases,
750751
movbe_target_feature,
751752
move_ref_pattern,
753+
move_size_limit,
752754
mul,
753755
mul_assign,
754756
mul_with_overflow,
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#![deny(large_assignments)]
2+
#![feature(large_assignments)]
3+
#![move_size_limit = "1000"]
4+
// build-fail
5+
// only-x86_64
6+
7+
// edition:2018
8+
9+
fn main() {
10+
let x = async { //~ ERROR large_assignments
11+
let y = [0; 9999];
12+
dbg!(y);
13+
thing(&y).await;
14+
dbg!(y);
15+
};
16+
let z = (x, 42); //~ ERROR large_assignments
17+
//~^ ERROR large_assignments
18+
let a = z.0; //~ ERROR large_assignments
19+
let b = z.1;
20+
}
21+
22+
async fn thing(y: &[u8]) {
23+
dbg!(y);
24+
}
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
error: moving 10024 bytes
2+
--> $DIR/large_moves.rs:10:13
3+
|
4+
LL | let x = async {
5+
| _____________^
6+
LL | | let y = [0; 9999];
7+
LL | | dbg!(y);
8+
LL | | thing(&y).await;
9+
LL | | dbg!(y);
10+
LL | | };
11+
| |_____^ value moved from here
12+
|
13+
note: the lint level is defined here
14+
--> $DIR/large_moves.rs:1:9
15+
|
16+
LL | #![deny(large_assignments)]
17+
| ^^^^^^^^^^^^^^^^^
18+
19+
error: moving 10024 bytes
20+
--> $DIR/large_moves.rs:16:14
21+
|
22+
LL | let z = (x, 42);
23+
| ^ value moved from here
24+
25+
error: moving 10024 bytes
26+
--> $DIR/large_moves.rs:16:13
27+
|
28+
LL | let z = (x, 42);
29+
| ^^^^^^^ value moved from here
30+
31+
error: moving 10024 bytes
32+
--> $DIR/large_moves.rs:18:13
33+
|
34+
LL | let a = z.0;
35+
| ^^^ value moved from here
36+
37+
error: aborting due to 4 previous errors
38+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// check that `move_size_limit is feature-gated
2+
3+
#![move_size_limit = "42"] //~ ERROR the `#[move_size_limit]` attribute is an experimental feature
4+
5+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0658]: the `#[move_size_limit]` attribute is an experimental feature
2+
--> $DIR/feature-gate-large-assignments.rs:3:1
3+
|
4+
LL | #![move_size_limit = "42"]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: see issue #83518 <https://github.com/rust-lang/rust/issues/83518> for more information
8+
= help: add `#![feature(large_assignments)]` to the crate attributes to enable
9+
10+
error: aborting due to previous error
11+
12+
For more information about this error, try `rustc --explain E0658`.

0 commit comments

Comments
 (0)