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