]>
Commit | Line | Data |
---|---|---|
0531ce1d XL |
1 | //! Propagates constants for early reporting of statically known |
2 | //! assertion failures | |
3 | ||
dc9dc135 XL |
4 | use std::cell::Cell; |
5 | ||
3dfed10e | 6 | use rustc_ast::Mutability; |
f035d41b | 7 | use rustc_data_structures::fx::FxHashSet; |
ba9703b0 XL |
8 | use rustc_hir::def::DefKind; |
9 | use rustc_hir::HirId; | |
f9f354fc | 10 | use rustc_index::bit_set::BitSet; |
ba9703b0 | 11 | use rustc_index::vec::IndexVec; |
ba9703b0 | 12 | use rustc_middle::mir::visit::{ |
dfeec247 XL |
13 | MutVisitor, MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor, |
14 | }; | |
ba9703b0 | 15 | use rustc_middle::mir::{ |
cdc7bbd5 XL |
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, | |
48663c56 | 19 | }; |
ba9703b0 XL |
20 | use rustc_middle::ty::layout::{HasTyCtxt, LayoutError, TyAndLayout}; |
21 | use rustc_middle::ty::subst::{InternalSubsts, Subst}; | |
29967ef6 XL |
22 | use rustc_middle::ty::{ |
23 | self, ConstInt, ConstKind, Instance, ParamEnv, ScalarInt, Ty, TyCtxt, TypeFoldable, | |
24 | }; | |
ba9703b0 XL |
25 | use rustc_session::lint; |
26 | use rustc_span::{def_id::DefId, Span}; | |
27 | use rustc_target::abi::{HasDataLayout, LayoutOf, Size, TargetDataLayout}; | |
5869c6ff | 28 | use rustc_target::spec::abi::Abi; |
ba9703b0 | 29 | use rustc_trait_selection::traits; |
0531ce1d | 30 | |
3dfed10e | 31 | use crate::const_eval::ConstEvalErr; |
dc9dc135 | 32 | use crate::interpret::{ |
29967ef6 XL |
33 | self, compile_time_machine, AllocId, Allocation, ConstValue, CtfeValidationMode, Frame, ImmTy, |
34 | Immediate, InterpCx, InterpResult, LocalState, LocalValue, MemPlace, Memory, MemoryKind, OpTy, | |
35 | Operand as InterpOperand, PlaceTy, Pointer, Scalar, ScalarMaybeUninit, StackPopCleanup, | |
17df50a5 | 36 | StackPopUnwind, |
a1dfa0c6 | 37 | }; |
29967ef6 | 38 | use crate::transform::MirPass; |
a1dfa0c6 | 39 | |
3dfed10e XL |
40 | /// The maximum number of bytes that we'll allocate space for a local or the return value. |
41 | /// Needed for #66397, because otherwise we eval into large places and that can cause OOM or just | |
42 | /// Severely regress performance. | |
e74abb32 XL |
43 | const MAX_ALLOC_LIMIT: u64 = 1024; |
44 | ||
ba9703b0 XL |
45 | /// Macro for machine-specific `InterpError` without allocation. |
46 | /// (These will never be shown to the user, but they help diagnose ICEs.) | |
47 | macro_rules! throw_machine_stop_str { | |
48 | ($($tt:tt)*) => {{ | |
49 | // We make a new local type for it. The type itself does not carry any information, | |
50 | // but its vtable (for the `MachineStopType` trait) does. | |
51 | struct Zst; | |
f9f354fc XL |
52 | // Printing this type shows the desired string. |
53 | impl std::fmt::Display for Zst { | |
ba9703b0 XL |
54 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
55 | write!(f, $($tt)*) | |
56 | } | |
57 | } | |
58 | impl rustc_middle::mir::interpret::MachineStopType for Zst {} | |
59 | throw_machine_stop!(Zst) | |
60 | }}; | |
61 | } | |
62 | ||
0531ce1d XL |
63 | pub struct ConstProp; |
64 | ||
e1599b0c | 65 | impl<'tcx> MirPass<'tcx> for ConstProp { |
29967ef6 | 66 | fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { |
0531ce1d | 67 | // will be evaluated by miri and produce its errors there |
29967ef6 | 68 | if body.source.promoted.is_some() { |
0531ce1d XL |
69 | return; |
70 | } | |
a1dfa0c6 | 71 | |
ba9703b0 | 72 | use rustc_middle::hir::map::blocks::FnLikeNode; |
29967ef6 XL |
73 | let def_id = body.source.def_id().expect_local(); |
74 | let hir_id = tcx.hir().local_def_id_to_hir_id(def_id); | |
a1dfa0c6 | 75 | |
dc9dc135 | 76 | let is_fn_like = FnLikeNode::from_node(tcx.hir().get(hir_id)).is_some(); |
29967ef6 | 77 | let is_assoc_const = tcx.def_kind(def_id.to_def_id()) == DefKind::AssocConst; |
a1dfa0c6 XL |
78 | |
79 | // Only run const prop on functions, methods, closures and associated constants | |
dfeec247 | 80 | if !is_fn_like && !is_assoc_const { |
a1dfa0c6 | 81 | // skip anon_const/statics/consts because they'll be evaluated by miri anyway |
29967ef6 | 82 | trace!("ConstProp skipped for {:?}", def_id); |
dfeec247 | 83 | return; |
0531ce1d | 84 | } |
a1dfa0c6 | 85 | |
29967ef6 | 86 | let is_generator = tcx.type_of(def_id.to_def_id()).is_generator(); |
e74abb32 XL |
87 | // FIXME(welseywiser) const prop doesn't work on generators because of query cycles |
88 | // computing their layout. | |
89 | if is_generator { | |
29967ef6 | 90 | trace!("ConstProp skipped for generator {:?}", def_id); |
dfeec247 XL |
91 | return; |
92 | } | |
93 | ||
94 | // Check if it's even possible to satisfy the 'where' clauses | |
95 | // for this item. | |
96 | // This branch will never be taken for any normal function. | |
97 | // However, it's possible to `#!feature(trivial_bounds)]` to write | |
98 | // a function with impossible to satisfy clauses, e.g.: | |
99 | // `fn foo() where String: Copy {}` | |
100 | // | |
101 | // We don't usually need to worry about this kind of case, | |
102 | // since we would get a compilation error if the user tried | |
103 | // to call it. However, since we can do const propagation | |
104 | // even without any calls to the function, we need to make | |
105 | // sure that it even makes sense to try to evaluate the body. | |
106 | // If there are unsatisfiable where clauses, then all bets are | |
107 | // off, and we just give up. | |
108 | // | |
109 | // We manually filter the predicates, skipping anything that's not | |
110 | // "global". We are in a potentially generic context | |
111 | // (e.g. we are evaluating a function without substituting generic | |
112 | // parameters, so this filtering serves two purposes: | |
113 | // | |
114 | // 1. We skip evaluating any predicates that we would | |
115 | // never be able prove are unsatisfiable (e.g. `<T as Foo>` | |
116 | // 2. We avoid trying to normalize predicates involving generic | |
117 | // parameters (e.g. `<T as Foo>::MyItem`). This can confuse | |
118 | // the normalization code (leading to cycle errors), since | |
119 | // it's usually never invoked in this way. | |
120 | let predicates = tcx | |
29967ef6 | 121 | .predicates_of(def_id.to_def_id()) |
dfeec247 XL |
122 | .predicates |
123 | .iter() | |
ba9703b0 | 124 | .filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None }); |
3dfed10e | 125 | if traits::impossible_predicates( |
dfeec247 | 126 | tcx, |
ba9703b0 | 127 | traits::elaborate_predicates(tcx, predicates).map(|o| o.predicate).collect(), |
dfeec247 | 128 | ) { |
29967ef6 | 129 | trace!("ConstProp skipped for {:?}: found unsatisfiable predicates", def_id); |
dfeec247 | 130 | return; |
e74abb32 XL |
131 | } |
132 | ||
29967ef6 | 133 | trace!("ConstProp starting for {:?}", def_id); |
0531ce1d | 134 | |
dfeec247 | 135 | let dummy_body = &Body::new( |
29967ef6 | 136 | body.source, |
dfeec247 XL |
137 | body.basic_blocks().clone(), |
138 | body.source_scopes.clone(), | |
139 | body.local_decls.clone(), | |
140 | Default::default(), | |
141 | body.arg_count, | |
142 | Default::default(), | |
5869c6ff | 143 | body.span, |
6a06907d | 144 | body.generator_kind(), |
dfeec247 | 145 | ); |
dc9dc135 | 146 | |
0531ce1d XL |
147 | // FIXME(oli-obk, eddyb) Optimize locals (or even local paths) to hold |
148 | // constants, instead of just checking for const-folding succeeding. | |
149 | // That would require an uniform one-def no-mutation analysis | |
150 | // and RPO (or recursing when needing the value of a local). | |
29967ef6 | 151 | let mut optimization_finder = ConstPropagator::new(body, dummy_body, tcx); |
dc9dc135 | 152 | optimization_finder.visit_body(body); |
0531ce1d | 153 | |
29967ef6 | 154 | trace!("ConstProp done for {:?}", def_id); |
0531ce1d XL |
155 | } |
156 | } | |
157 | ||
ba9703b0 XL |
158 | struct ConstPropMachine<'mir, 'tcx> { |
159 | /// The virtual call stack. | |
160 | stack: Vec<Frame<'mir, 'tcx, (), ()>>, | |
f035d41b XL |
161 | /// `OnlyInsideOwnBlock` locals that were written in the current block get erased at the end. |
162 | written_only_inside_own_block_locals: FxHashSet<Local>, | |
163 | /// Locals that need to be cleared after every block terminates. | |
164 | only_propagate_inside_block_locals: BitSet<Local>, | |
3dfed10e | 165 | can_const_prop: IndexVec<Local, ConstPropMode>, |
ba9703b0 XL |
166 | } |
167 | ||
168 | impl<'mir, 'tcx> ConstPropMachine<'mir, 'tcx> { | |
3dfed10e XL |
169 | fn new( |
170 | only_propagate_inside_block_locals: BitSet<Local>, | |
171 | can_const_prop: IndexVec<Local, ConstPropMode>, | |
172 | ) -> Self { | |
f035d41b XL |
173 | Self { |
174 | stack: Vec::new(), | |
175 | written_only_inside_own_block_locals: Default::default(), | |
176 | only_propagate_inside_block_locals, | |
3dfed10e | 177 | can_const_prop, |
f035d41b | 178 | } |
ba9703b0 XL |
179 | } |
180 | } | |
e74abb32 | 181 | |
ba9703b0 | 182 | impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx> { |
f9f354fc | 183 | compile_time_machine!(<'mir, 'tcx>); |
e74abb32 | 184 | |
fc512014 XL |
185 | type MemoryKind = !; |
186 | ||
e74abb32 | 187 | type MemoryExtra = (); |
e74abb32 | 188 | |
5869c6ff XL |
189 | fn load_mir( |
190 | _ecx: &InterpCx<'mir, 'tcx, Self>, | |
191 | _instance: ty::InstanceDef<'tcx>, | |
192 | ) -> InterpResult<'tcx, &'tcx Body<'tcx>> { | |
193 | throw_machine_stop_str!("calling functions isn't supported in ConstProp") | |
194 | } | |
195 | ||
60c5eb7d | 196 | fn find_mir_or_eval_fn( |
e74abb32 XL |
197 | _ecx: &mut InterpCx<'mir, 'tcx, Self>, |
198 | _instance: ty::Instance<'tcx>, | |
5869c6ff | 199 | _abi: Abi, |
e74abb32 | 200 | _args: &[OpTy<'tcx>], |
6a06907d | 201 | _ret: Option<(&PlaceTy<'tcx>, BasicBlock)>, |
17df50a5 | 202 | _unwind: StackPopUnwind, |
e74abb32 XL |
203 | ) -> InterpResult<'tcx, Option<&'mir Body<'tcx>>> { |
204 | Ok(None) | |
205 | } | |
206 | ||
e74abb32 XL |
207 | fn call_intrinsic( |
208 | _ecx: &mut InterpCx<'mir, 'tcx, Self>, | |
e74abb32 XL |
209 | _instance: ty::Instance<'tcx>, |
210 | _args: &[OpTy<'tcx>], | |
6a06907d | 211 | _ret: Option<(&PlaceTy<'tcx>, BasicBlock)>, |
17df50a5 | 212 | _unwind: StackPopUnwind, |
60c5eb7d | 213 | ) -> InterpResult<'tcx> { |
ba9703b0 | 214 | throw_machine_stop_str!("calling intrinsics isn't supported in ConstProp") |
60c5eb7d XL |
215 | } |
216 | ||
217 | fn assert_panic( | |
218 | _ecx: &mut InterpCx<'mir, 'tcx, Self>, | |
ba9703b0 XL |
219 | _msg: &rustc_middle::mir::AssertMessage<'tcx>, |
220 | _unwind: Option<rustc_middle::mir::BasicBlock>, | |
e74abb32 | 221 | ) -> InterpResult<'tcx> { |
ba9703b0 | 222 | bug!("panics terminators are not evaluated in ConstProp") |
e74abb32 XL |
223 | } |
224 | ||
dfeec247 | 225 | fn ptr_to_int(_mem: &Memory<'mir, 'tcx, Self>, _ptr: Pointer) -> InterpResult<'tcx, u64> { |
ba9703b0 | 226 | throw_unsup!(ReadPointerAsBytes) |
e74abb32 XL |
227 | } |
228 | ||
229 | fn binary_ptr_op( | |
230 | _ecx: &InterpCx<'mir, 'tcx, Self>, | |
231 | _bin_op: BinOp, | |
6a06907d XL |
232 | _left: &ImmTy<'tcx>, |
233 | _right: &ImmTy<'tcx>, | |
e74abb32 XL |
234 | ) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)> { |
235 | // We can't do this because aliasing of memory can differ between const eval and llvm | |
ba9703b0 | 236 | throw_machine_stop_str!("pointer arithmetic or comparisons aren't supported in ConstProp") |
e74abb32 XL |
237 | } |
238 | ||
e74abb32 XL |
239 | fn box_alloc( |
240 | _ecx: &mut InterpCx<'mir, 'tcx, Self>, | |
6a06907d | 241 | _dest: &PlaceTy<'tcx>, |
e74abb32 | 242 | ) -> InterpResult<'tcx> { |
ba9703b0 | 243 | throw_machine_stop_str!("can't const prop heap allocations") |
e74abb32 XL |
244 | } |
245 | ||
246 | fn access_local( | |
247 | _ecx: &InterpCx<'mir, 'tcx, Self>, | |
248 | frame: &Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>, | |
249 | local: Local, | |
250 | ) -> InterpResult<'tcx, InterpOperand<Self::PointerTag>> { | |
251 | let l = &frame.locals[local]; | |
252 | ||
253 | if l.value == LocalValue::Uninitialized { | |
ba9703b0 | 254 | throw_machine_stop_str!("tried to access an uninitialized local") |
e74abb32 XL |
255 | } |
256 | ||
257 | l.access() | |
258 | } | |
259 | ||
f035d41b XL |
260 | fn access_local_mut<'a>( |
261 | ecx: &'a mut InterpCx<'mir, 'tcx, Self>, | |
262 | frame: usize, | |
263 | local: Local, | |
264 | ) -> InterpResult<'tcx, Result<&'a mut LocalValue<Self::PointerTag>, MemPlace<Self::PointerTag>>> | |
265 | { | |
3dfed10e XL |
266 | if ecx.machine.can_const_prop[local] == ConstPropMode::NoPropagation { |
267 | throw_machine_stop_str!("tried to write to a local that is marked as not propagatable") | |
268 | } | |
f035d41b | 269 | if frame == 0 && ecx.machine.only_propagate_inside_block_locals.contains(local) { |
3dfed10e XL |
270 | trace!( |
271 | "mutating local {:?} which is restricted to its block. \ | |
272 | Will remove it from const-prop after block is finished.", | |
273 | local | |
274 | ); | |
f035d41b XL |
275 | ecx.machine.written_only_inside_own_block_locals.insert(local); |
276 | } | |
277 | ecx.machine.stack[frame].locals[local].access_mut() | |
278 | } | |
279 | ||
ba9703b0 | 280 | fn before_access_global( |
dfeec247 | 281 | _memory_extra: &(), |
ba9703b0 | 282 | _alloc_id: AllocId, |
e74abb32 | 283 | allocation: &Allocation<Self::PointerTag, Self::AllocExtra>, |
ba9703b0 XL |
284 | _static_def_id: Option<DefId>, |
285 | is_write: bool, | |
e74abb32 | 286 | ) -> InterpResult<'tcx> { |
ba9703b0 XL |
287 | if is_write { |
288 | throw_machine_stop_str!("can't write to global"); | |
289 | } | |
290 | // If the static allocation is mutable, then we can't const prop it as its content | |
291 | // might be different at runtime. | |
292 | if allocation.mutability == Mutability::Mut { | |
293 | throw_machine_stop_str!("can't access mutable globals in ConstProp"); | |
e74abb32 XL |
294 | } |
295 | ||
296 | Ok(()) | |
297 | } | |
298 | ||
3dfed10e XL |
299 | #[inline(always)] |
300 | fn init_frame_extra( | |
301 | _ecx: &mut InterpCx<'mir, 'tcx, Self>, | |
302 | frame: Frame<'mir, 'tcx>, | |
303 | ) -> InterpResult<'tcx, Frame<'mir, 'tcx>> { | |
304 | Ok(frame) | |
305 | } | |
306 | ||
ba9703b0 XL |
307 | #[inline(always)] |
308 | fn stack( | |
309 | ecx: &'a InterpCx<'mir, 'tcx, Self>, | |
310 | ) -> &'a [Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>] { | |
311 | &ecx.machine.stack | |
312 | } | |
313 | ||
314 | #[inline(always)] | |
315 | fn stack_mut( | |
316 | ecx: &'a mut InterpCx<'mir, 'tcx, Self>, | |
317 | ) -> &'a mut Vec<Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>> { | |
318 | &mut ecx.machine.stack | |
e74abb32 | 319 | } |
e74abb32 XL |
320 | } |
321 | ||
0531ce1d | 322 | /// Finds optimization opportunities on the MIR. |
dc9dc135 | 323 | struct ConstPropagator<'mir, 'tcx> { |
ba9703b0 | 324 | ecx: InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, |
dc9dc135 | 325 | tcx: TyCtxt<'tcx>, |
0531ce1d | 326 | param_env: ParamEnv<'tcx>, |
60c5eb7d XL |
327 | // FIXME(eddyb) avoid cloning these two fields more than once, |
328 | // by accessing them through `ecx` instead. | |
29967ef6 | 329 | source_scopes: IndexVec<SourceScope, SourceScopeData<'tcx>>, |
48663c56 | 330 | local_decls: IndexVec<Local, LocalDecl<'tcx>>, |
dfeec247 XL |
331 | // Because we have `MutVisitor` we can't obtain the `SourceInfo` from a `Location`. So we store |
332 | // the last known `SourceInfo` here and just keep revisiting it. | |
333 | source_info: Option<SourceInfo>, | |
0531ce1d XL |
334 | } |
335 | ||
dc9dc135 | 336 | impl<'mir, 'tcx> LayoutOf for ConstPropagator<'mir, 'tcx> { |
48663c56 | 337 | type Ty = Ty<'tcx>; |
ba9703b0 | 338 | type TyAndLayout = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>; |
0531ce1d | 339 | |
ba9703b0 | 340 | fn layout_of(&self, ty: Ty<'tcx>) -> Self::TyAndLayout { |
0531ce1d XL |
341 | self.tcx.layout_of(self.param_env.and(ty)) |
342 | } | |
343 | } | |
344 | ||
dc9dc135 | 345 | impl<'mir, 'tcx> HasDataLayout for ConstPropagator<'mir, 'tcx> { |
0531ce1d XL |
346 | #[inline] |
347 | fn data_layout(&self) -> &TargetDataLayout { | |
348 | &self.tcx.data_layout | |
349 | } | |
350 | } | |
351 | ||
dc9dc135 | 352 | impl<'mir, 'tcx> HasTyCtxt<'tcx> for ConstPropagator<'mir, 'tcx> { |
0531ce1d | 353 | #[inline] |
dc9dc135 | 354 | fn tcx(&self) -> TyCtxt<'tcx> { |
0531ce1d XL |
355 | self.tcx |
356 | } | |
357 | } | |
358 | ||
dc9dc135 | 359 | impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> { |
0531ce1d | 360 | fn new( |
f9f354fc | 361 | body: &Body<'tcx>, |
dc9dc135 | 362 | dummy_body: &'mir Body<'tcx>, |
dc9dc135 | 363 | tcx: TyCtxt<'tcx>, |
dc9dc135 | 364 | ) -> ConstPropagator<'mir, 'tcx> { |
29967ef6 | 365 | let def_id = body.source.def_id(); |
dfeec247 | 366 | let substs = &InternalSubsts::identity_for_item(tcx, def_id); |
3dfed10e | 367 | let param_env = tcx.param_env_reveal_all_normalized(def_id); |
dfeec247 | 368 | |
dc9dc135 | 369 | let span = tcx.def_span(def_id); |
3dfed10e XL |
370 | // FIXME: `CanConstProp::check` computes the layout of all locals, return those layouts |
371 | // so we can write them to `ecx.frame_mut().locals.layout, reducing the duplication in | |
372 | // `layout_of` query invocations. | |
373 | let can_const_prop = CanConstProp::check(tcx, param_env, body); | |
f035d41b XL |
374 | let mut only_propagate_inside_block_locals = BitSet::new_empty(can_const_prop.len()); |
375 | for (l, mode) in can_const_prop.iter_enumerated() { | |
376 | if *mode == ConstPropMode::OnlyInsideOwnBlock { | |
377 | only_propagate_inside_block_locals.insert(l); | |
378 | } | |
379 | } | |
380 | let mut ecx = InterpCx::new( | |
381 | tcx, | |
382 | span, | |
383 | param_env, | |
3dfed10e | 384 | ConstPropMachine::new(only_propagate_inside_block_locals, can_const_prop), |
f035d41b XL |
385 | (), |
386 | ); | |
dc9dc135 | 387 | |
dfeec247 XL |
388 | let ret = ecx |
389 | .layout_of(body.return_ty().subst(tcx, substs)) | |
390 | .ok() | |
391 | // Don't bother allocating memory for ZST types which have no values | |
392 | // or for large values. | |
393 | .filter(|ret_layout| { | |
394 | !ret_layout.is_zst() && ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT) | |
395 | }) | |
6a06907d | 396 | .map(|ret_layout| ecx.allocate(ret_layout, MemoryKind::Stack).into()); |
60c5eb7d | 397 | |
dc9dc135 | 398 | ecx.push_stack_frame( |
60c5eb7d | 399 | Instance::new(def_id, substs), |
dc9dc135 | 400 | dummy_body, |
6a06907d | 401 | ret.as_ref(), |
dfeec247 XL |
402 | StackPopCleanup::None { cleanup: false }, |
403 | ) | |
404 | .expect("failed to push initial stack frame"); | |
48663c56 | 405 | |
0531ce1d | 406 | ConstPropagator { |
94b46f34 | 407 | ecx, |
0531ce1d | 408 | tcx, |
0531ce1d | 409 | param_env, |
60c5eb7d XL |
410 | // FIXME(eddyb) avoid cloning these two fields more than once, |
411 | // by accessing them through `ecx` instead. | |
412 | source_scopes: body.source_scopes.clone(), | |
dc9dc135 XL |
413 | //FIXME(wesleywiser) we can't steal this because `Visitor::super_visit_body()` needs it |
414 | local_decls: body.local_decls.clone(), | |
dfeec247 | 415 | source_info: None, |
0531ce1d XL |
416 | } |
417 | } | |
418 | ||
f9f354fc | 419 | fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> { |
f035d41b XL |
420 | let op = match self.ecx.eval_place_to_op(place, None) { |
421 | Ok(op) => op, | |
422 | Err(e) => { | |
423 | trace!("get_const failed: {}", e); | |
424 | return None; | |
425 | } | |
426 | }; | |
60c5eb7d | 427 | |
f9f354fc XL |
428 | // Try to read the local as an immediate so that if it is representable as a scalar, we can |
429 | // handle it as such, but otherwise, just return the value as is. | |
6a06907d | 430 | Some(match self.ecx.try_read_immediate(&op) { |
f035d41b | 431 | Ok(Ok(imm)) => imm.into(), |
f9f354fc | 432 | _ => op, |
f035d41b | 433 | }) |
dc9dc135 XL |
434 | } |
435 | ||
f9f354fc XL |
436 | /// Remove `local` from the pool of `Locals`. Allows writing to them, |
437 | /// but not reading from them anymore. | |
438 | fn remove_const(ecx: &mut InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, local: Local) { | |
439 | ecx.frame_mut().locals[local] = | |
dfeec247 | 440 | LocalState { value: LocalValue::Uninitialized, layout: Cell::new(None) }; |
dc9dc135 XL |
441 | } |
442 | ||
dfeec247 | 443 | fn lint_root(&self, source_info: SourceInfo) -> Option<HirId> { |
cdc7bbd5 | 444 | source_info.scope.lint_root(&self.source_scopes) |
dfeec247 XL |
445 | } |
446 | ||
74b04a01 | 447 | fn use_ecx<F, T>(&mut self, f: F) -> Option<T> |
94b46f34 | 448 | where |
dc9dc135 | 449 | F: FnOnce(&mut Self) -> InterpResult<'tcx, T>, |
94b46f34 | 450 | { |
ba9703b0 | 451 | match f(self) { |
94b46f34 | 452 | Ok(val) => Some(val), |
8faf50e0 | 453 | Err(error) => { |
3dfed10e | 454 | trace!("InterpCx operation failed: {:?}", error); |
74b04a01 XL |
455 | // Some errors shouldn't come up because creating them causes |
456 | // an allocation, which we should avoid. When that happens, | |
457 | // dedicated error variants should be introduced instead. | |
ba9703b0 | 458 | assert!( |
6a06907d XL |
459 | !error.kind().formatted_string(), |
460 | "const-prop encountered formatting error: {}", | |
74b04a01 XL |
461 | error |
462 | ); | |
94b46f34 | 463 | None |
dfeec247 | 464 | } |
ba9703b0 | 465 | } |
94b46f34 XL |
466 | } |
467 | ||
f9f354fc | 468 | /// Returns the value, if any, of evaluating `c`. |
dfeec247 | 469 | fn eval_constant(&mut self, c: &Constant<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> { |
dfeec247 XL |
470 | // FIXME we need to revisit this for #67176 |
471 | if c.needs_subst() { | |
472 | return None; | |
473 | } | |
474 | ||
6a06907d | 475 | match self.ecx.mir_const_to_op(&c.literal, None) { |
dfeec247 | 476 | Ok(op) => Some(op), |
8faf50e0 | 477 | Err(error) => { |
f035d41b | 478 | let tcx = self.ecx.tcx.at(c.span); |
3dfed10e | 479 | let err = ConstEvalErr::new(&self.ecx, error, Some(c.span)); |
dfeec247 | 480 | if let Some(lint_root) = self.lint_root(source_info) { |
6a06907d XL |
481 | let lint_only = match c.literal { |
482 | ConstantKind::Ty(ct) => match ct.val { | |
483 | // Promoteds must lint and not error as the user didn't ask for them | |
cdc7bbd5 XL |
484 | ConstKind::Unevaluated(ty::Unevaluated { |
485 | def: _, | |
486 | substs: _, | |
487 | promoted: Some(_), | |
488 | }) => true, | |
6a06907d XL |
489 | // Out of backwards compatibility we cannot report hard errors in unused |
490 | // generic functions using associated constants of the generic parameters. | |
491 | _ => c.literal.needs_subst(), | |
492 | }, | |
493 | ConstantKind::Val(_, ty) => ty.needs_subst(), | |
dfeec247 XL |
494 | }; |
495 | if lint_only { | |
496 | // Out of backwards compatibility we cannot report hard errors in unused | |
497 | // generic functions using associated constants of the generic parameters. | |
f035d41b | 498 | err.report_as_lint(tcx, "erroneous constant used", lint_root, Some(c.span)); |
dfeec247 | 499 | } else { |
f035d41b | 500 | err.report_as_error(tcx, "erroneous constant used"); |
dfeec247 XL |
501 | } |
502 | } else { | |
f035d41b | 503 | err.report_as_error(tcx, "erroneous constant used"); |
dfeec247 | 504 | } |
8faf50e0 | 505 | None |
dfeec247 | 506 | } |
8faf50e0 XL |
507 | } |
508 | } | |
509 | ||
f9f354fc | 510 | /// Returns the value, if any, of evaluating `place`. |
ba9703b0 | 511 | fn eval_place(&mut self, place: Place<'tcx>) -> Option<OpTy<'tcx>> { |
dc9dc135 | 512 | trace!("eval_place(place={:?})", place); |
74b04a01 | 513 | self.use_ecx(|this| this.ecx.eval_place_to_op(place, None)) |
0531ce1d XL |
514 | } |
515 | ||
f9f354fc XL |
516 | /// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant` |
517 | /// or `eval_place`, depending on the variant of `Operand` used. | |
dfeec247 | 518 | fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> { |
0531ce1d | 519 | match *op { |
dfeec247 | 520 | Operand::Constant(ref c) => self.eval_constant(c, source_info), |
ba9703b0 | 521 | Operand::Move(place) | Operand::Copy(place) => self.eval_place(place), |
0531ce1d XL |
522 | } |
523 | } | |
524 | ||
74b04a01 XL |
525 | fn report_assert_as_lint( |
526 | &self, | |
527 | lint: &'static lint::Lint, | |
528 | source_info: SourceInfo, | |
529 | message: &'static str, | |
f9652781 | 530 | panic: AssertKind<impl std::fmt::Debug>, |
74b04a01 XL |
531 | ) -> Option<()> { |
532 | let lint_root = self.lint_root(source_info)?; | |
533 | self.tcx.struct_span_lint_hir(lint, lint_root, source_info.span, |lint| { | |
534 | let mut err = lint.build(message); | |
535 | err.span_label(source_info.span, format!("{:?}", panic)); | |
536 | err.emit() | |
537 | }); | |
ba9703b0 | 538 | None |
74b04a01 | 539 | } |
dfeec247 | 540 | |
74b04a01 XL |
541 | fn check_unary_op( |
542 | &mut self, | |
543 | op: UnOp, | |
544 | arg: &Operand<'tcx>, | |
545 | source_info: SourceInfo, | |
546 | ) -> Option<()> { | |
f035d41b | 547 | if let (val, true) = self.use_ecx(|this| { |
6a06907d XL |
548 | let val = this.ecx.read_immediate(&this.ecx.eval_operand(arg, None)?)?; |
549 | let (_res, overflow, _ty) = this.ecx.overflowing_unary_op(op, &val)?; | |
f035d41b | 550 | Ok((val, overflow)) |
74b04a01 XL |
551 | })? { |
552 | // `AssertKind` only has an `OverflowNeg` variant, so make sure that is | |
553 | // appropriate to use. | |
554 | assert_eq!(op, UnOp::Neg, "Neg is the only UnOp that can overflow"); | |
555 | self.report_assert_as_lint( | |
556 | lint::builtin::ARITHMETIC_OVERFLOW, | |
557 | source_info, | |
558 | "this arithmetic operation will overflow", | |
f035d41b | 559 | AssertKind::OverflowNeg(val.to_const_int()), |
74b04a01 XL |
560 | )?; |
561 | } | |
dfeec247 XL |
562 | |
563 | Some(()) | |
564 | } | |
565 | ||
566 | fn check_binary_op( | |
567 | &mut self, | |
568 | op: BinOp, | |
569 | left: &Operand<'tcx>, | |
570 | right: &Operand<'tcx>, | |
571 | source_info: SourceInfo, | |
dfeec247 | 572 | ) -> Option<()> { |
6a06907d XL |
573 | let r = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?)); |
574 | let l = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?)); | |
74b04a01 | 575 | // Check for exceeding shifts *even if* we cannot evaluate the LHS. |
dfeec247 | 576 | if op == BinOp::Shr || op == BinOp::Shl { |
3dfed10e | 577 | let r = r?; |
74b04a01 XL |
578 | // We need the type of the LHS. We cannot use `place_layout` as that is the type |
579 | // of the result, which for checked binops is not the same! | |
580 | let left_ty = left.ty(&self.local_decls, self.tcx); | |
f035d41b | 581 | let left_size = self.ecx.layout_of(left_ty).ok()?.size; |
dfeec247 | 582 | let right_size = r.layout.size; |
74b04a01 XL |
583 | let r_bits = r.to_scalar().ok(); |
584 | // This is basically `force_bits`. | |
585 | let r_bits = r_bits.and_then(|r| r.to_bits_or_ptr(right_size, &self.tcx).ok()); | |
f035d41b XL |
586 | if r_bits.map_or(false, |b| b >= left_size.bits() as u128) { |
587 | debug!("check_binary_op: reporting assert for {:?}", source_info); | |
74b04a01 XL |
588 | self.report_assert_as_lint( |
589 | lint::builtin::ARITHMETIC_OVERFLOW, | |
590 | source_info, | |
591 | "this arithmetic operation will overflow", | |
f035d41b XL |
592 | AssertKind::Overflow( |
593 | op, | |
594 | match l { | |
595 | Some(l) => l.to_const_int(), | |
596 | // Invent a dummy value, the diagnostic ignores it anyway | |
597 | None => ConstInt::new( | |
29967ef6 | 598 | ScalarInt::try_from_uint(1_u8, left_size).unwrap(), |
f035d41b XL |
599 | left_ty.is_signed(), |
600 | left_ty.is_ptr_sized_integral(), | |
601 | ), | |
602 | }, | |
603 | r.to_const_int(), | |
604 | ), | |
74b04a01 | 605 | )?; |
dfeec247 XL |
606 | } |
607 | } | |
608 | ||
6a06907d | 609 | if let (Some(l), Some(r)) = (&l, &r) { |
3dfed10e XL |
610 | // The remaining operators are handled through `overflowing_binary_op`. |
611 | if self.use_ecx(|this| { | |
612 | let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?; | |
613 | Ok(overflow) | |
614 | })? { | |
615 | self.report_assert_as_lint( | |
616 | lint::builtin::ARITHMETIC_OVERFLOW, | |
617 | source_info, | |
618 | "this arithmetic operation will overflow", | |
619 | AssertKind::Overflow(op, l.to_const_int(), r.to_const_int()), | |
620 | )?; | |
621 | } | |
dfeec247 | 622 | } |
dfeec247 XL |
623 | Some(()) |
624 | } | |
625 | ||
3dfed10e XL |
626 | fn propagate_operand(&mut self, operand: &mut Operand<'tcx>) { |
627 | match *operand { | |
628 | Operand::Copy(l) | Operand::Move(l) => { | |
629 | if let Some(value) = self.get_const(l) { | |
6a06907d | 630 | if self.should_const_prop(&value) { |
3dfed10e XL |
631 | // FIXME(felix91gr): this code only handles `Scalar` cases. |
632 | // For now, we're not handling `ScalarPair` cases because | |
633 | // doing so here would require a lot of code duplication. | |
634 | // We should hopefully generalize `Operand` handling into a fn, | |
635 | // and use it to do const-prop here and everywhere else | |
636 | // where it makes sense. | |
637 | if let interpret::Operand::Immediate(interpret::Immediate::Scalar( | |
638 | ScalarMaybeUninit::Scalar(scalar), | |
639 | )) = *value | |
640 | { | |
641 | *operand = self.operand_from_scalar( | |
642 | scalar, | |
643 | value.layout.ty, | |
644 | self.source_info.unwrap().span, | |
645 | ); | |
646 | } | |
647 | } | |
648 | } | |
649 | } | |
650 | Operand::Constant(_) => (), | |
651 | } | |
652 | } | |
653 | ||
0531ce1d XL |
654 | fn const_prop( |
655 | &mut self, | |
656 | rvalue: &Rvalue<'tcx>, | |
0531ce1d | 657 | source_info: SourceInfo, |
ba9703b0 | 658 | place: Place<'tcx>, |
e74abb32 | 659 | ) -> Option<()> { |
e74abb32 XL |
660 | // Perform any special handling for specific Rvalue types. |
661 | // Generally, checks here fall into one of two categories: | |
662 | // 1. Additional checking to provide useful lints to the user | |
663 | // - In this case, we will do some validation and then fall through to the | |
664 | // end of the function which evals the assignment. | |
665 | // 2. Working around bugs in other parts of the compiler | |
666 | // - In this case, we'll return `None` from this function to stop evaluation. | |
667 | match rvalue { | |
74b04a01 XL |
668 | // Additional checking: give lints to the user if an overflow would occur. |
669 | // We do this here and not in the `Assert` terminator as that terminator is | |
670 | // only sometimes emitted (overflow checks can be disabled), but we want to always | |
671 | // lint. | |
672 | Rvalue::UnaryOp(op, arg) => { | |
673 | trace!("checking UnaryOp(op = {:?}, arg = {:?})", op, arg); | |
674 | self.check_unary_op(*op, arg, source_info)?; | |
0531ce1d | 675 | } |
6a06907d | 676 | Rvalue::BinaryOp(op, box (left, right)) => { |
e74abb32 | 677 | trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right); |
74b04a01 XL |
678 | self.check_binary_op(*op, left, right, source_info)?; |
679 | } | |
6a06907d | 680 | Rvalue::CheckedBinaryOp(op, box (left, right)) => { |
74b04a01 XL |
681 | trace!( |
682 | "checking CheckedBinaryOp(op = {:?}, left = {:?}, right = {:?})", | |
683 | op, | |
684 | left, | |
685 | right | |
686 | ); | |
687 | self.check_binary_op(*op, left, right, source_info)?; | |
e74abb32 XL |
688 | } |
689 | ||
dfeec247 | 690 | // Do not try creating references (#67862) |
f035d41b XL |
691 | Rvalue::AddressOf(_, place) | Rvalue::Ref(_, _, place) => { |
692 | trace!("skipping AddressOf | Ref for {:?}", place); | |
693 | ||
694 | // This may be creating mutable references or immutable references to cells. | |
695 | // If that happens, the pointed to value could be mutated via that reference. | |
696 | // Since we aren't tracking references, the const propagator loses track of what | |
697 | // value the local has right now. | |
698 | // Thus, all locals that have their reference taken | |
699 | // must not take part in propagation. | |
700 | Self::remove_const(&mut self.ecx, place.local); | |
e74abb32 | 701 | |
dfeec247 | 702 | return None; |
e74abb32 | 703 | } |
f035d41b XL |
704 | Rvalue::ThreadLocalRef(def_id) => { |
705 | trace!("skipping ThreadLocalRef({:?})", def_id); | |
e74abb32 | 706 | |
f035d41b XL |
707 | return None; |
708 | } | |
709 | ||
710 | // There's no other checking to do at this time. | |
711 | Rvalue::Aggregate(..) | |
712 | | Rvalue::Use(..) | |
713 | | Rvalue::Repeat(..) | |
714 | | Rvalue::Len(..) | |
715 | | Rvalue::Cast(..) | |
716 | | Rvalue::Discriminant(..) | |
717 | | Rvalue::NullaryOp(..) => {} | |
0531ce1d | 718 | } |
e74abb32 | 719 | |
f9f354fc XL |
720 | // FIXME we need to revisit this for #67176 |
721 | if rvalue.needs_subst() { | |
722 | return None; | |
723 | } | |
724 | ||
6a06907d | 725 | if self.tcx.sess.mir_opt_level() >= 4 { |
3dfed10e XL |
726 | self.eval_rvalue_with_identities(rvalue, place) |
727 | } else { | |
728 | self.use_ecx(|this| this.ecx.eval_rvalue_into_place(rvalue, place)) | |
729 | } | |
730 | } | |
731 | ||
732 | // Attempt to use albegraic identities to eliminate constant expressions | |
733 | fn eval_rvalue_with_identities( | |
734 | &mut self, | |
735 | rvalue: &Rvalue<'tcx>, | |
736 | place: Place<'tcx>, | |
737 | ) -> Option<()> { | |
74b04a01 | 738 | self.use_ecx(|this| { |
3dfed10e | 739 | match rvalue { |
6a06907d XL |
740 | Rvalue::BinaryOp(op, box (left, right)) |
741 | | Rvalue::CheckedBinaryOp(op, box (left, right)) => { | |
3dfed10e XL |
742 | let l = this.ecx.eval_operand(left, None); |
743 | let r = this.ecx.eval_operand(right, None); | |
744 | ||
745 | let const_arg = match (l, r) { | |
6a06907d | 746 | (Ok(ref x), Err(_)) | (Err(_), Ok(ref x)) => this.ecx.read_immediate(x)?, |
3dfed10e XL |
747 | (Err(e), Err(_)) => return Err(e), |
748 | (Ok(_), Ok(_)) => { | |
749 | this.ecx.eval_rvalue_into_place(rvalue, place)?; | |
750 | return Ok(()); | |
751 | } | |
752 | }; | |
753 | ||
754 | let arg_value = | |
755 | this.ecx.force_bits(const_arg.to_scalar()?, const_arg.layout.size)?; | |
756 | let dest = this.ecx.eval_place(place)?; | |
757 | ||
758 | match op { | |
759 | BinOp::BitAnd => { | |
760 | if arg_value == 0 { | |
6a06907d | 761 | this.ecx.write_immediate(*const_arg, &dest)?; |
3dfed10e XL |
762 | } |
763 | } | |
764 | BinOp::BitOr => { | |
29967ef6 | 765 | if arg_value == const_arg.layout.size.truncate(u128::MAX) |
3dfed10e XL |
766 | || (const_arg.layout.ty.is_bool() && arg_value == 1) |
767 | { | |
6a06907d | 768 | this.ecx.write_immediate(*const_arg, &dest)?; |
3dfed10e XL |
769 | } |
770 | } | |
771 | BinOp::Mul => { | |
772 | if const_arg.layout.ty.is_integral() && arg_value == 0 { | |
6a06907d | 773 | if let Rvalue::CheckedBinaryOp(_, _) = rvalue { |
3dfed10e XL |
774 | let val = Immediate::ScalarPair( |
775 | const_arg.to_scalar()?.into(), | |
776 | Scalar::from_bool(false).into(), | |
777 | ); | |
6a06907d | 778 | this.ecx.write_immediate(val, &dest)?; |
3dfed10e | 779 | } else { |
6a06907d | 780 | this.ecx.write_immediate(*const_arg, &dest)?; |
3dfed10e XL |
781 | } |
782 | } | |
783 | } | |
784 | _ => { | |
785 | this.ecx.eval_rvalue_into_place(rvalue, place)?; | |
786 | } | |
787 | } | |
788 | } | |
789 | _ => { | |
790 | this.ecx.eval_rvalue_into_place(rvalue, place)?; | |
791 | } | |
792 | } | |
793 | ||
e74abb32 XL |
794 | Ok(()) |
795 | }) | |
0531ce1d | 796 | } |
48663c56 | 797 | |
f9f354fc | 798 | /// Creates a new `Operand::Constant` from a `Scalar` value |
48663c56 | 799 | fn operand_from_scalar(&self, scalar: Scalar, ty: Ty<'tcx>, span: Span) -> Operand<'tcx> { |
dfeec247 XL |
800 | Operand::Constant(Box::new(Constant { |
801 | span, | |
802 | user_ty: None, | |
6a06907d | 803 | literal: ty::Const::from_scalar(self.tcx, scalar, ty).into(), |
dfeec247 | 804 | })) |
48663c56 XL |
805 | } |
806 | ||
dc9dc135 XL |
807 | fn replace_with_const( |
808 | &mut self, | |
809 | rval: &mut Rvalue<'tcx>, | |
6a06907d | 810 | value: &OpTy<'tcx>, |
dc9dc135 XL |
811 | source_info: SourceInfo, |
812 | ) { | |
f9f354fc | 813 | if let Rvalue::Use(Operand::Constant(c)) = rval { |
6a06907d XL |
814 | match c.literal { |
815 | ConstantKind::Ty(c) if matches!(c.val, ConstKind::Unevaluated(..)) => {} | |
816 | _ => { | |
817 | trace!("skipping replace of Rvalue::Use({:?} because it is already a const", c); | |
818 | return; | |
819 | } | |
f9f354fc XL |
820 | } |
821 | } | |
822 | ||
fc512014 | 823 | trace!("attempting to replace {:?} with {:?}", rval, value); |
74b04a01 | 824 | if let Err(e) = self.ecx.const_validate_operand( |
48663c56 XL |
825 | value, |
826 | vec![], | |
dc9dc135 | 827 | // FIXME: is ref tracking too expensive? |
29967ef6 | 828 | // FIXME: what is the point of ref tracking if we do not even check the tracked refs? |
74b04a01 | 829 | &mut interpret::RefTracking::empty(), |
29967ef6 | 830 | CtfeValidationMode::Regular, |
dc9dc135 XL |
831 | ) { |
832 | trace!("validation error, attempt failed: {:?}", e); | |
833 | return; | |
834 | } | |
835 | ||
74b04a01 XL |
836 | // FIXME> figure out what to do when try_read_immediate fails |
837 | let imm = self.use_ecx(|this| this.ecx.try_read_immediate(value)); | |
48663c56 | 838 | |
dc9dc135 XL |
839 | if let Some(Ok(imm)) = imm { |
840 | match *imm { | |
f9f354fc | 841 | interpret::Immediate::Scalar(ScalarMaybeUninit::Scalar(scalar)) => { |
dfeec247 XL |
842 | *rval = Rvalue::Use(self.operand_from_scalar( |
843 | scalar, | |
844 | value.layout.ty, | |
845 | source_info.span, | |
846 | )); | |
847 | } | |
48663c56 | 848 | Immediate::ScalarPair( |
3dfed10e XL |
849 | ScalarMaybeUninit::Scalar(_), |
850 | ScalarMaybeUninit::Scalar(_), | |
48663c56 | 851 | ) => { |
3dfed10e XL |
852 | // Found a value represented as a pair. For now only do const-prop if the type |
853 | // of `rvalue` is also a tuple with two scalars. | |
854 | // FIXME: enable the general case stated above ^. | |
855 | let ty = &value.layout.ty; | |
60c5eb7d | 856 | // Only do it for tuples |
1b1a35ee | 857 | if let ty::Tuple(substs) = ty.kind() { |
60c5eb7d XL |
858 | // Only do it if tuple is also a pair with two scalars |
859 | if substs.len() == 2 { | |
3dfed10e | 860 | let alloc = self.use_ecx(|this| { |
60c5eb7d XL |
861 | let ty1 = substs[0].expect_ty(); |
862 | let ty2 = substs[1].expect_ty(); | |
863 | let ty_is_scalar = |ty| { | |
ba9703b0 | 864 | this.ecx.layout_of(ty).ok().map(|layout| layout.abi.is_scalar()) |
60c5eb7d XL |
865 | == Some(true) |
866 | }; | |
867 | if ty_is_scalar(ty1) && ty_is_scalar(ty2) { | |
3dfed10e XL |
868 | let alloc = this |
869 | .ecx | |
870 | .intern_with_temp_alloc(value.layout, |ecx, dest| { | |
871 | ecx.write_immediate_to_mplace(*imm, dest) | |
872 | }) | |
873 | .unwrap(); | |
874 | Ok(Some(alloc)) | |
60c5eb7d XL |
875 | } else { |
876 | Ok(None) | |
877 | } | |
878 | }); | |
879 | ||
3dfed10e XL |
880 | if let Some(Some(alloc)) = alloc { |
881 | // Assign entire constant in a single statement. | |
882 | // We can't use aggregates, as we run after the aggregate-lowering `MirPhase`. | |
883 | *rval = Rvalue::Use(Operand::Constant(Box::new(Constant { | |
884 | span: source_info.span, | |
885 | user_ty: None, | |
6a06907d XL |
886 | literal: self |
887 | .ecx | |
888 | .tcx | |
889 | .mk_const(ty::Const { | |
890 | ty, | |
891 | val: ty::ConstKind::Value(ConstValue::ByRef { | |
892 | alloc, | |
893 | offset: Size::ZERO, | |
894 | }), | |
895 | }) | |
896 | .into(), | |
3dfed10e | 897 | }))); |
60c5eb7d XL |
898 | } |
899 | } | |
48663c56 | 900 | } |
dfeec247 | 901 | } |
3dfed10e XL |
902 | // Scalars or scalar pairs that contain undef values are assumed to not have |
903 | // successfully evaluated and are thus not propagated. | |
dfeec247 | 904 | _ => {} |
48663c56 XL |
905 | } |
906 | } | |
907 | } | |
908 | ||
f9f354fc | 909 | /// Returns `true` if and only if this `op` should be const-propagated into. |
6a06907d XL |
910 | fn should_const_prop(&mut self, op: &OpTy<'tcx>) -> bool { |
911 | let mir_opt_level = self.tcx.sess.mir_opt_level(); | |
60c5eb7d XL |
912 | |
913 | if mir_opt_level == 0 { | |
914 | return false; | |
915 | } | |
916 | ||
fc512014 XL |
917 | if !self.tcx.consider_optimizing(|| format!("ConstantPropagation - OpTy: {:?}", op)) { |
918 | return false; | |
919 | } | |
920 | ||
6a06907d | 921 | match **op { |
f9f354fc | 922 | interpret::Operand::Immediate(Immediate::Scalar(ScalarMaybeUninit::Scalar(s))) => { |
dfeec247 XL |
923 | s.is_bits() |
924 | } | |
925 | interpret::Operand::Immediate(Immediate::ScalarPair( | |
f9f354fc XL |
926 | ScalarMaybeUninit::Scalar(l), |
927 | ScalarMaybeUninit::Scalar(r), | |
dfeec247 | 928 | )) => l.is_bits() && r.is_bits(), |
dfeec247 | 929 | _ => false, |
60c5eb7d | 930 | } |
48663c56 | 931 | } |
0531ce1d XL |
932 | } |
933 | ||
dfeec247 XL |
934 | /// The mode that `ConstProp` is allowed to run in for a given `Local`. |
935 | #[derive(Clone, Copy, Debug, PartialEq)] | |
936 | enum ConstPropMode { | |
937 | /// The `Local` can be propagated into and reads of this `Local` can also be propagated. | |
938 | FullConstProp, | |
f9f354fc XL |
939 | /// The `Local` can only be propagated into and from its own block. |
940 | OnlyInsideOwnBlock, | |
dfeec247 XL |
941 | /// The `Local` can be propagated into but reads cannot be propagated. |
942 | OnlyPropagateInto, | |
f035d41b XL |
943 | /// The `Local` cannot be part of propagation at all. Any statement |
944 | /// referencing it either for reading or writing will not get propagated. | |
dfeec247 XL |
945 | NoPropagation, |
946 | } | |
947 | ||
0531ce1d | 948 | struct CanConstProp { |
dfeec247 | 949 | can_const_prop: IndexVec<Local, ConstPropMode>, |
f9f354fc XL |
950 | // False at the beginning. Once set, no more assignments are allowed to that local. |
951 | found_assignment: BitSet<Local>, | |
952 | // Cache of locals' information | |
953 | local_kinds: IndexVec<Local, LocalKind>, | |
0531ce1d XL |
954 | } |
955 | ||
956 | impl CanConstProp { | |
f9f354fc | 957 | /// Returns true if `local` can be propagated |
3dfed10e XL |
958 | fn check( |
959 | tcx: TyCtxt<'tcx>, | |
960 | param_env: ParamEnv<'tcx>, | |
961 | body: &Body<'tcx>, | |
962 | ) -> IndexVec<Local, ConstPropMode> { | |
0531ce1d | 963 | let mut cpv = CanConstProp { |
dfeec247 | 964 | can_const_prop: IndexVec::from_elem(ConstPropMode::FullConstProp, &body.local_decls), |
f9f354fc XL |
965 | found_assignment: BitSet::new_empty(body.local_decls.len()), |
966 | local_kinds: IndexVec::from_fn_n( | |
967 | |local| body.local_kind(local), | |
968 | body.local_decls.len(), | |
969 | ), | |
0531ce1d XL |
970 | }; |
971 | for (local, val) in cpv.can_const_prop.iter_enumerated_mut() { | |
3dfed10e XL |
972 | let ty = body.local_decls[local].ty; |
973 | match tcx.layout_of(param_env.and(ty)) { | |
974 | Ok(layout) if layout.size < Size::from_bytes(MAX_ALLOC_LIMIT) => {} | |
975 | // Either the layout fails to compute, then we can't use this local anyway | |
976 | // or the local is too large, then we don't want to. | |
977 | _ => { | |
978 | *val = ConstPropMode::NoPropagation; | |
979 | continue; | |
980 | } | |
981 | } | |
f9f354fc XL |
982 | // Cannot use args at all |
983 | // Cannot use locals because if x < y { y - x } else { x - y } would | |
0531ce1d XL |
984 | // lint for x != y |
985 | // FIXME(oli-obk): lint variables until they are used in a condition | |
986 | // FIXME(oli-obk): lint if return value is constant | |
f9f354fc | 987 | if cpv.local_kinds[local] == LocalKind::Arg { |
dfeec247 | 988 | *val = ConstPropMode::OnlyPropagateInto; |
f9f354fc XL |
989 | trace!( |
990 | "local {:?} can't be const propagated because it's a function argument", | |
991 | local | |
992 | ); | |
993 | } else if cpv.local_kinds[local] == LocalKind::Var { | |
994 | *val = ConstPropMode::OnlyInsideOwnBlock; | |
995 | trace!( | |
996 | "local {:?} will only be propagated inside its block, because it's a user variable", | |
997 | local | |
998 | ); | |
dc9dc135 | 999 | } |
0531ce1d | 1000 | } |
ba9703b0 | 1001 | cpv.visit_body(&body); |
0531ce1d XL |
1002 | cpv.can_const_prop |
1003 | } | |
1004 | } | |
1005 | ||
1006 | impl<'tcx> Visitor<'tcx> for CanConstProp { | |
dfeec247 | 1007 | fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) { |
ba9703b0 | 1008 | use rustc_middle::mir::visit::PlaceContext::*; |
0531ce1d | 1009 | match context { |
f9f354fc XL |
1010 | // Projections are fine, because `&mut foo.x` will be caught by |
1011 | // `MutatingUseContext::Borrow` elsewhere. | |
1012 | MutatingUse(MutatingUseContext::Projection) | |
1013 | // These are just stores, where the storing is not propagatable, but there may be later | |
1014 | // mutations of the same local via `Store` | |
1015 | | MutatingUse(MutatingUseContext::Call) | |
1016 | // Actual store that can possibly even propagate a value | |
1017 | | MutatingUse(MutatingUseContext::Store) => { | |
1018 | if !self.found_assignment.insert(local) { | |
1019 | match &mut self.can_const_prop[local] { | |
1020 | // If the local can only get propagated in its own block, then we don't have | |
1021 | // to worry about multiple assignments, as we'll nuke the const state at the | |
1022 | // end of the block anyway, and inside the block we overwrite previous | |
1023 | // states as applicable. | |
1024 | ConstPropMode::OnlyInsideOwnBlock => {} | |
f035d41b XL |
1025 | ConstPropMode::NoPropagation => {} |
1026 | ConstPropMode::OnlyPropagateInto => {} | |
1027 | other @ ConstPropMode::FullConstProp => { | |
f9f354fc | 1028 | trace!( |
3dfed10e XL |
1029 | "local {:?} can't be propagated because of multiple assignments. Previous state: {:?}", |
1030 | local, other, | |
f9f354fc | 1031 | ); |
3dfed10e | 1032 | *other = ConstPropMode::OnlyInsideOwnBlock; |
f9f354fc XL |
1033 | } |
1034 | } | |
dfeec247 XL |
1035 | } |
1036 | } | |
0531ce1d | 1037 | // Reading constants is allowed an arbitrary number of times |
dfeec247 XL |
1038 | NonMutatingUse(NonMutatingUseContext::Copy) |
1039 | | NonMutatingUse(NonMutatingUseContext::Move) | |
1040 | | NonMutatingUse(NonMutatingUseContext::Inspect) | |
1041 | | NonMutatingUse(NonMutatingUseContext::Projection) | |
dfeec247 | 1042 | | NonUse(_) => {} |
f9f354fc XL |
1043 | |
1044 | // These could be propagated with a smarter analysis or just some careful thinking about | |
1045 | // whether they'd be fine right now. | |
1046 | MutatingUse(MutatingUseContext::AsmOutput) | |
1047 | | MutatingUse(MutatingUseContext::Yield) | |
1048 | | MutatingUse(MutatingUseContext::Drop) | |
1049 | | MutatingUse(MutatingUseContext::Retag) | |
1050 | // These can't ever be propagated under any scheme, as we can't reason about indirect | |
1051 | // mutation. | |
1052 | | NonMutatingUse(NonMutatingUseContext::SharedBorrow) | |
1053 | | NonMutatingUse(NonMutatingUseContext::ShallowBorrow) | |
1054 | | NonMutatingUse(NonMutatingUseContext::UniqueBorrow) | |
1055 | | NonMutatingUse(NonMutatingUseContext::AddressOf) | |
1056 | | MutatingUse(MutatingUseContext::Borrow) | |
1057 | | MutatingUse(MutatingUseContext::AddressOf) => { | |
dc9dc135 | 1058 | trace!("local {:?} can't be propagaged because it's used: {:?}", local, context); |
dfeec247 XL |
1059 | self.can_const_prop[local] = ConstPropMode::NoPropagation; |
1060 | } | |
0531ce1d XL |
1061 | } |
1062 | } | |
1063 | } | |
1064 | ||
dc9dc135 | 1065 | impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> { |
e74abb32 XL |
1066 | fn tcx(&self) -> TyCtxt<'tcx> { |
1067 | self.tcx | |
1068 | } | |
1069 | ||
f9f354fc XL |
1070 | fn visit_body(&mut self, body: &mut Body<'tcx>) { |
1071 | for (bb, data) in body.basic_blocks_mut().iter_enumerated_mut() { | |
1072 | self.visit_basic_block_data(bb, data); | |
1073 | } | |
1074 | } | |
1075 | ||
3dfed10e XL |
1076 | fn visit_operand(&mut self, operand: &mut Operand<'tcx>, location: Location) { |
1077 | self.super_operand(operand, location); | |
1078 | ||
6a06907d | 1079 | // Only const prop copies and moves on `mir_opt_level=3` as doing so |
1b1a35ee | 1080 | // currently slightly increases compile time in some cases. |
6a06907d | 1081 | if self.tcx.sess.mir_opt_level() >= 3 { |
3dfed10e XL |
1082 | self.propagate_operand(operand) |
1083 | } | |
1084 | } | |
1085 | ||
dfeec247 | 1086 | fn visit_constant(&mut self, constant: &mut Constant<'tcx>, location: Location) { |
0531ce1d XL |
1087 | trace!("visit_constant: {:?}", constant); |
1088 | self.super_constant(constant, location); | |
dfeec247 | 1089 | self.eval_constant(constant, self.source_info.unwrap()); |
0531ce1d XL |
1090 | } |
1091 | ||
dfeec247 | 1092 | fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) { |
0531ce1d | 1093 | trace!("visit_statement: {:?}", statement); |
dfeec247 XL |
1094 | let source_info = statement.source_info; |
1095 | self.source_info = Some(source_info); | |
ba9703b0 | 1096 | if let StatementKind::Assign(box (place, ref mut rval)) = statement.kind { |
3dfed10e XL |
1097 | let can_const_prop = self.ecx.machine.can_const_prop[place.local]; |
1098 | if let Some(()) = self.const_prop(rval, source_info, place) { | |
1099 | // This will return None if the above `const_prop` invocation only "wrote" a | |
1100 | // type whose creation requires no write. E.g. a generator whose initial state | |
1101 | // consists solely of uninitialized memory (so it doesn't capture any locals). | |
6a06907d | 1102 | if let Some(ref value) = self.get_const(place) { |
3dfed10e XL |
1103 | if self.should_const_prop(value) { |
1104 | trace!("replacing {:?} with {:?}", rval, value); | |
1105 | self.replace_with_const(rval, value, source_info); | |
1106 | if can_const_prop == ConstPropMode::FullConstProp | |
1107 | || can_const_prop == ConstPropMode::OnlyInsideOwnBlock | |
1108 | { | |
1109 | trace!("propagated into {:?}", place); | |
dfeec247 XL |
1110 | } |
1111 | } | |
3dfed10e XL |
1112 | } |
1113 | match can_const_prop { | |
1114 | ConstPropMode::OnlyInsideOwnBlock => { | |
1115 | trace!( | |
1116 | "found local restricted to its block. \ | |
f035d41b | 1117 | Will remove it from const-prop after block is finished. Local: {:?}", |
3dfed10e XL |
1118 | place.local |
1119 | ); | |
1120 | } | |
1121 | ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => { | |
1122 | trace!("can't propagate into {:?}", place); | |
1123 | if place.local != RETURN_PLACE { | |
1124 | Self::remove_const(&mut self.ecx, place.local); | |
8faf50e0 | 1125 | } |
0531ce1d | 1126 | } |
3dfed10e | 1127 | ConstPropMode::FullConstProp => {} |
0531ce1d | 1128 | } |
f035d41b | 1129 | } else { |
3dfed10e XL |
1130 | // Const prop failed, so erase the destination, ensuring that whatever happens |
1131 | // from here on, does not know about the previous value. | |
1132 | // This is important in case we have | |
1133 | // ```rust | |
1134 | // let mut x = 42; | |
1135 | // x = SOME_MUTABLE_STATIC; | |
1136 | // // x must now be uninit | |
1137 | // ``` | |
1138 | // FIXME: we overzealously erase the entire local, because that's easier to | |
1139 | // implement. | |
f035d41b | 1140 | trace!( |
3dfed10e XL |
1141 | "propagation into {:?} failed. |
1142 | Nuking the entire site from orbit, it's the only way to be sure", | |
f035d41b XL |
1143 | place, |
1144 | ); | |
1145 | Self::remove_const(&mut self.ecx, place.local); | |
0531ce1d | 1146 | } |
e74abb32 XL |
1147 | } else { |
1148 | match statement.kind { | |
3dfed10e XL |
1149 | StatementKind::SetDiscriminant { ref place, .. } => { |
1150 | match self.ecx.machine.can_const_prop[place.local] { | |
1151 | ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => { | |
1152 | if self.use_ecx(|this| this.ecx.statement(statement)).is_some() { | |
1153 | trace!("propped discriminant into {:?}", place); | |
1154 | } else { | |
1155 | Self::remove_const(&mut self.ecx, place.local); | |
1156 | } | |
1157 | } | |
1158 | ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => { | |
1159 | Self::remove_const(&mut self.ecx, place.local); | |
1160 | } | |
1161 | } | |
1162 | } | |
dfeec247 | 1163 | StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => { |
e74abb32 XL |
1164 | let frame = self.ecx.frame_mut(); |
1165 | frame.locals[local].value = | |
1166 | if let StatementKind::StorageLive(_) = statement.kind { | |
1167 | LocalValue::Uninitialized | |
1168 | } else { | |
1169 | LocalValue::Dead | |
1170 | }; | |
1171 | } | |
1172 | _ => {} | |
1173 | } | |
0531ce1d | 1174 | } |
e74abb32 | 1175 | |
48663c56 | 1176 | self.super_statement(statement, location); |
0531ce1d XL |
1177 | } |
1178 | ||
dfeec247 | 1179 | fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Location) { |
48663c56 | 1180 | let source_info = terminator.source_info; |
dfeec247 XL |
1181 | self.source_info = Some(source_info); |
1182 | self.super_terminator(terminator, location); | |
48663c56 | 1183 | match &mut terminator.kind { |
416331ca | 1184 | TerminatorKind::Assert { expected, ref msg, ref mut cond, .. } => { |
6a06907d | 1185 | if let Some(ref value) = self.eval_operand(&cond, source_info) { |
48663c56 | 1186 | trace!("assertion on {:?} should be {:?}", value, expected); |
f9f354fc | 1187 | let expected = ScalarMaybeUninit::from(Scalar::from_bool(*expected)); |
6a06907d | 1188 | let value_const = self.ecx.read_scalar(&value).unwrap(); |
48663c56 | 1189 | if expected != value_const { |
f9652781 XL |
1190 | enum DbgVal<T> { |
1191 | Val(T), | |
1192 | Underscore, | |
1193 | } | |
1194 | impl<T: std::fmt::Debug> std::fmt::Debug for DbgVal<T> { | |
1195 | fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
1196 | match self { | |
1197 | Self::Val(val) => val.fmt(fmt), | |
1198 | Self::Underscore => fmt.write_str("_"), | |
1199 | } | |
1200 | } | |
1201 | } | |
f035d41b | 1202 | let mut eval_to_int = |op| { |
f9652781 XL |
1203 | // This can be `None` if the lhs wasn't const propagated and we just |
1204 | // triggered the assert on the value of the rhs. | |
1205 | match self.eval_operand(op, source_info) { | |
6a06907d XL |
1206 | Some(op) => DbgVal::Val( |
1207 | self.ecx.read_immediate(&op).unwrap().to_const_int(), | |
1208 | ), | |
f9652781 XL |
1209 | None => DbgVal::Underscore, |
1210 | } | |
f035d41b XL |
1211 | }; |
1212 | let msg = match msg { | |
1213 | AssertKind::DivisionByZero(op) => { | |
1214 | Some(AssertKind::DivisionByZero(eval_to_int(op))) | |
1215 | } | |
1216 | AssertKind::RemainderByZero(op) => { | |
1217 | Some(AssertKind::RemainderByZero(eval_to_int(op))) | |
1218 | } | |
1219 | AssertKind::BoundsCheck { ref len, ref index } => { | |
1220 | let len = eval_to_int(len); | |
1221 | let index = eval_to_int(index); | |
1222 | Some(AssertKind::BoundsCheck { len, index }) | |
1223 | } | |
1224 | // Overflow is are already covered by checks on the binary operators. | |
1225 | AssertKind::Overflow(..) | AssertKind::OverflowNeg(_) => None, | |
1226 | // Need proper const propagator for these. | |
1227 | _ => None, | |
1228 | }; | |
f9f354fc | 1229 | // Poison all places this operand references so that further code |
48663c56 XL |
1230 | // doesn't use the invalid value |
1231 | match cond { | |
1232 | Operand::Move(ref place) | Operand::Copy(ref place) => { | |
f9f354fc | 1233 | Self::remove_const(&mut self.ecx, place.local); |
dfeec247 | 1234 | } |
48663c56 XL |
1235 | Operand::Constant(_) => {} |
1236 | } | |
f035d41b XL |
1237 | if let Some(msg) = msg { |
1238 | self.report_assert_as_lint( | |
1239 | lint::builtin::UNCONDITIONAL_PANIC, | |
1240 | source_info, | |
1241 | "this operation will panic at runtime", | |
1242 | msg, | |
1243 | ); | |
1244 | } | |
48663c56 | 1245 | } else { |
60c5eb7d | 1246 | if self.should_const_prop(value) { |
f9f354fc | 1247 | if let ScalarMaybeUninit::Scalar(scalar) = value_const { |
48663c56 XL |
1248 | *cond = self.operand_from_scalar( |
1249 | scalar, | |
1250 | self.tcx.types.bool, | |
1251 | source_info.span, | |
1252 | ); | |
0531ce1d | 1253 | } |
48663c56 | 1254 | } |
0531ce1d | 1255 | } |
0531ce1d | 1256 | } |
dfeec247 | 1257 | } |
3dfed10e XL |
1258 | TerminatorKind::SwitchInt { ref mut discr, .. } => { |
1259 | // FIXME: This is currently redundant with `visit_operand`, but sadly | |
1260 | // always visiting operands currently causes a perf regression in LLVM codegen, so | |
6a06907d | 1261 | // `visit_operand` currently only runs for propagates places for `mir_opt_level=4`. |
3dfed10e | 1262 | self.propagate_operand(discr) |
dfeec247 | 1263 | } |
3dfed10e | 1264 | // None of these have Operands to const-propagate. |
dfeec247 XL |
1265 | TerminatorKind::Goto { .. } |
1266 | | TerminatorKind::Resume | |
1267 | | TerminatorKind::Abort | |
1268 | | TerminatorKind::Return | |
1269 | | TerminatorKind::Unreachable | |
1270 | | TerminatorKind::Drop { .. } | |
1271 | | TerminatorKind::DropAndReplace { .. } | |
1272 | | TerminatorKind::Yield { .. } | |
1273 | | TerminatorKind::GeneratorDrop | |
f035d41b | 1274 | | TerminatorKind::FalseEdge { .. } |
f9f354fc XL |
1275 | | TerminatorKind::FalseUnwind { .. } |
1276 | | TerminatorKind::InlineAsm { .. } => {} | |
3dfed10e XL |
1277 | // Every argument in our function calls have already been propagated in `visit_operand`. |
1278 | // | |
1b1a35ee | 1279 | // NOTE: because LLVM codegen gives slight performance regressions with it, so this is |
6a06907d | 1280 | // gated on `mir_opt_level=3`. |
3dfed10e | 1281 | TerminatorKind::Call { .. } => {} |
f9f354fc | 1282 | } |
f035d41b XL |
1283 | |
1284 | // We remove all Locals which are restricted in propagation to their containing blocks and | |
1285 | // which were modified in the current block. | |
3dfed10e | 1286 | // Take it out of the ecx so we can get a mutable reference to the ecx for `remove_const`. |
f035d41b XL |
1287 | let mut locals = std::mem::take(&mut self.ecx.machine.written_only_inside_own_block_locals); |
1288 | for &local in locals.iter() { | |
f9f354fc | 1289 | Self::remove_const(&mut self.ecx, local); |
0531ce1d | 1290 | } |
f035d41b XL |
1291 | locals.clear(); |
1292 | // Put it back so we reuse the heap of the storage | |
1293 | self.ecx.machine.written_only_inside_own_block_locals = locals; | |
1294 | if cfg!(debug_assertions) { | |
1295 | // Ensure we are correctly erasing locals with the non-debug-assert logic. | |
1296 | for local in self.ecx.machine.only_propagate_inside_block_locals.iter() { | |
1297 | assert!( | |
1298 | self.get_const(local.into()).is_none() | |
1299 | || self | |
1300 | .layout_of(self.local_decls[local].ty) | |
1301 | .map_or(true, |layout| layout.is_zst()) | |
1302 | ) | |
1303 | } | |
1304 | } | |
0531ce1d XL |
1305 | } |
1306 | } |