Skip to content

Commit e9696c8

Browse files
oli-obkpnkfelix
authored andcommitted
Implement a lint that highlights all moves larger than 1000 bytes
1 parent 6af1e63 commit e9696c8

File tree

6 files changed

+153
-14
lines changed

6 files changed

+153
-14
lines changed

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2877,6 +2877,39 @@ declare_lint! {
28772877
};
28782878
}
28792879

2880+
declare_lint! {
2881+
/// The `large_assigments` 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+
Allow,
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/mir/mod.rs

Lines changed: 21 additions & 1 deletion
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,26 @@ 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(self, source_scopes: &IndexVec<SourceScope, SourceScopeData<'tcx>>) -> Option<HirId> {
1956+
let mut data = &source_scopes[self];
1957+
// FIXME(oli-obk): we should be able to just walk the `inlined_parent_scope`, but it
1958+
// does not work as I thought it would. Needs more investigation and documentation.
1959+
while data.inlined.is_some() {
1960+
trace!(?data);
1961+
data = &source_scopes[data.parent_scope.unwrap()];
1962+
}
1963+
trace!(?data);
1964+
match &data.local_data {
1965+
ClearCrossCrate::Set(data) => Some(data.lint_root),
1966+
ClearCrossCrate::Clear => None,
1967+
}
1968+
}
1969+
}
1970+
19511971
#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable, TypeFoldable)]
19521972
pub struct SourceScopeData<'tcx> {
19531973
pub span: Span,

compiler/rustc_mir/src/monomorphize/collector.rs

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

compiler/rustc_mir/src/transform/const_prop.rs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ 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,
16+
AssertKind, BasicBlock, BinOp, Body, Constant, ConstantKind, Local, LocalDecl,
1717
LocalKind, Location, Operand, Place, Rvalue, SourceInfo, SourceScope, SourceScopeData,
1818
Statement, StatementKind, Terminator, TerminatorKind, UnOp, RETURN_PLACE,
1919
};
@@ -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>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![deny(large_assignments)]
2+
// build-fail
3+
4+
// edition:2018
5+
6+
fn main() {
7+
let x = async { //~ ERROR large_assignments
8+
let y = [0; 9999];
9+
dbg!(y);
10+
thing(&y).await;
11+
dbg!(y);
12+
};
13+
let z = (x, 42); //~ ERROR large_assignments
14+
//~^ ERROR large_assignments
15+
let a = z.0; //~ ERROR large_assignments
16+
let b = z.1;
17+
}
18+
19+
async fn thing(y: &[u8]) {
20+
dbg!(y);
21+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
error: moving 10024 bytes
2+
--> $DIR/large_moves.rs:7: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:13:14
21+
|
22+
LL | let z = (x, 42);
23+
| ^ value moved from here
24+
25+
error: moving 10024 bytes
26+
--> $DIR/large_moves.rs:13:13
27+
|
28+
LL | let z = (x, 42);
29+
| ^^^^^^^ value moved from here
30+
31+
error: moving 10024 bytes
32+
--> $DIR/large_moves.rs:15:13
33+
|
34+
LL | let a = z.0;
35+
| ^^^ value moved from here
36+
37+
error: aborting due to 4 previous errors
38+

0 commit comments

Comments
 (0)