1 //! Propagates constants for early reporting of statically known
4 use rustc::hir::def::DefKind;
6 AggregateKind, Constant, Location, Place, PlaceBase, Mir, Operand, Rvalue, Local,
7 NullOp, UnOp, StatementKind, Statement, LocalKind, Static, StaticKind,
8 TerminatorKind, Terminator, ClearCrossCrate, SourceInfo, BinOp, ProjectionElem,
9 SourceScope, SourceScopeLocalData, LocalDecl, Promoted,
11 use rustc::mir::visit::{
12 Visitor, PlaceContext, MutatingUseContext, MutVisitor, NonMutatingUseContext,
14 use rustc::mir::interpret::{InterpError, Scalar, GlobalId, EvalResult};
15 use rustc::ty::{self, Instance, ParamEnv, Ty, TyCtxt};
16 use syntax_pos::{Span, DUMMY_SP};
17 use rustc::ty::subst::InternalSubsts;
18 use rustc_data_structures::indexed_vec::IndexVec;
19 use rustc::ty::layout::{
20 LayoutOf, TyLayout, LayoutError,
21 HasTyCtxt, TargetDataLayout, HasDataLayout,
24 use crate::interpret::{self, InterpretCx, ScalarMaybeUndef, Immediate, OpTy, ImmTy, MemoryKind};
25 use crate::const_eval::{
26 CompileTimeInterpreter, error_to_const_error, eval_promoted, mk_eval_cx,
28 use crate::transform::{MirPass, MirSource};
32 impl MirPass for ConstProp {
33 fn run_pass<'a, 'tcx>(&self,
34 tcx: TyCtxt<'a, 'tcx, 'tcx>,
35 source: MirSource<'tcx>,
36 mir: &mut Mir<'tcx>) {
37 // will be evaluated by miri and produce its errors there
38 if source.promoted.is_some() {
42 use rustc::hir::map::blocks::FnLikeNode;
43 let hir_id = tcx.hir().as_local_hir_id(source.def_id())
44 .expect("Non-local call to local provider is_const_fn");
46 let is_fn_like = FnLikeNode::from_node(tcx.hir().get_by_hir_id(hir_id)).is_some();
47 let is_assoc_const = match tcx.def_kind(source.def_id()) {
48 Some(DefKind::AssociatedConst) => true,
52 // Only run const prop on functions, methods, closures and associated constants
53 if !is_fn_like && !is_assoc_const {
54 // skip anon_const/statics/consts because they'll be evaluated by miri anyway
55 trace!("ConstProp skipped for {:?}", source.def_id());
59 trace!("ConstProp starting for {:?}", source.def_id());
61 // FIXME(oli-obk, eddyb) Optimize locals (or even local paths) to hold
62 // constants, instead of just checking for const-folding succeeding.
63 // That would require an uniform one-def no-mutation analysis
64 // and RPO (or recursing when needing the value of a local).
65 let mut optimization_finder = ConstPropagator::new(mir, tcx, source);
66 optimization_finder.visit_mir(mir);
68 // put back the data we stole from `mir`
70 &mut mir.source_scope_local_data,
71 optimization_finder.source_scope_local_data
75 optimization_finder.promoted
78 trace!("ConstProp done for {:?}", source.def_id());
82 type Const<'tcx> = OpTy<'tcx>;
84 /// Finds optimization opportunities on the MIR.
85 struct ConstPropagator<'a, 'mir, 'tcx:'a+'mir> {
86 ecx: InterpretCx<'a, 'mir, 'tcx, CompileTimeInterpreter<'a, 'mir, 'tcx>>,
87 tcx: TyCtxt<'a, 'tcx, 'tcx>,
88 source: MirSource<'tcx>,
89 places: IndexVec<Local, Option<Const<'tcx>>>,
90 can_const_prop: IndexVec<Local, bool>,
91 param_env: ParamEnv<'tcx>,
92 source_scope_local_data: ClearCrossCrate<IndexVec<SourceScope, SourceScopeLocalData>>,
93 local_decls: IndexVec<Local, LocalDecl<'tcx>>,
94 promoted: IndexVec<Promoted, Mir<'tcx>>,
97 impl<'a, 'b, 'tcx> LayoutOf for ConstPropagator<'a, 'b, 'tcx> {
99 type TyLayout = Result<TyLayout<'tcx>, LayoutError<'tcx>>;
101 fn layout_of(&self, ty: Ty<'tcx>) -> Self::TyLayout {
102 self.tcx.layout_of(self.param_env.and(ty))
106 impl<'a, 'b, 'tcx> HasDataLayout for ConstPropagator<'a, 'b, 'tcx> {
108 fn data_layout(&self) -> &TargetDataLayout {
109 &self.tcx.data_layout
113 impl<'a, 'b, 'tcx> HasTyCtxt<'tcx> for ConstPropagator<'a, 'b, 'tcx> {
115 fn tcx<'c>(&'c self) -> TyCtxt<'c, 'tcx, 'tcx> {
120 impl<'a, 'mir, 'tcx> ConstPropagator<'a, 'mir, 'tcx> {
123 tcx: TyCtxt<'a, 'tcx, 'tcx>,
124 source: MirSource<'tcx>,
125 ) -> ConstPropagator<'a, 'mir, 'tcx> {
126 let param_env = tcx.param_env(source.def_id());
127 let ecx = mk_eval_cx(tcx, tcx.def_span(source.def_id()), param_env);
128 let can_const_prop = CanConstProp::check(mir);
129 let source_scope_local_data = std::mem::replace(
130 &mut mir.source_scope_local_data,
131 ClearCrossCrate::Clear
133 let promoted = std::mem::replace(
144 places: IndexVec::from_elem(None, &mir.local_decls),
145 source_scope_local_data,
146 //FIXME(wesleywiser) we can't steal this because `Visitor::super_visit_mir()` needs it
147 local_decls: mir.local_decls.clone(),
154 source_info: SourceInfo,
158 F: FnOnce(&mut Self) -> EvalResult<'tcx, T>,
160 self.ecx.tcx.span = source_info.span;
161 let lint_root = match self.source_scope_local_data {
162 ClearCrossCrate::Set(ref ivs) => {
163 //FIXME(#51314): remove this check
164 if source_info.scope.index() >= ivs.len() {
167 ivs[source_info.scope].lint_root
169 ClearCrossCrate::Clear => return None,
171 let r = match f(self) {
172 Ok(val) => Some(val),
174 let diagnostic = error_to_const_error(&self.ecx, error);
175 use rustc::mir::interpret::InterpError::*;
176 match diagnostic.error {
177 // don't report these, they make no sense in a const prop context
180 // at runtime these transformations might make sense
181 // FIXME: figure out the rules and start linting
182 | FunctionAbiMismatch(..)
183 | FunctionArgMismatch(..)
184 | FunctionRetMismatch(..)
185 | FunctionArgCountMismatch
186 // fine at runtime, might be a register address or sth
191 // don't report const evaluator limits
192 | StackFrameLimitReached
197 | InvalidMemoryAccess
198 | DanglingPointerDeref
200 | InvalidFunctionPointer
202 | InvalidDiscriminant(..)
203 | PointerOutOfBounds { .. }
204 | InvalidNullPointerUsage
205 | ValidationFailure(..)
210 | DerefFunctionPointer
215 | AlignmentCheckFailed{..}
216 | CalledClosureAsFunction
217 | VtableForArgumentlessMethod
218 | ModifiedConstantMemory
221 // FIXME: should probably be removed and turned into a bug! call
222 | TypeNotPrimitive(_)
223 | ReallocatedWrongMemoryKind(_, _)
224 | DeallocatedWrongMemoryKind(_, _)
225 | ReallocateNonBasePtr
226 | DeallocateNonBasePtr
227 | IncorrectAllocationInformation(..)
228 | UnterminatedCString(_)
230 | HeapAllocNonPowerOfTwoAlignment(_)
232 | ReadFromReturnPointer
233 | GeneratorResumedAfterReturn
234 | GeneratorResumedAfterPanic
238 // FIXME: report UB here
244 => bug!("these should not be in rustc, but in miri's machine errors"),
247 | UnimplementedTraitSelection
250 // these are just noise
265 diagnostic.report_as_lint(
267 "this expression will panic at runtime",
276 self.ecx.tcx.span = DUMMY_SP;
283 ) -> Option<Const<'tcx>> {
284 self.ecx.tcx.span = c.span;
285 match self.ecx.eval_const_to_op(*c.literal, None) {
290 let err = error_to_const_error(&self.ecx, error);
291 err.report_as_error(self.ecx.tcx, "erroneous constant used");
297 fn eval_place(&mut self, place: &Place<'tcx>, source_info: SourceInfo) -> Option<Const<'tcx>> {
299 Place::Base(PlaceBase::Local(loc)) => self.places[loc].clone(),
300 Place::Projection(ref proj) => match proj.elem {
301 ProjectionElem::Field(field, _) => {
302 trace!("field proj on {:?}", proj.base);
303 let base = self.eval_place(&proj.base, source_info)?;
304 let res = self.use_ecx(source_info, |this| {
305 this.ecx.operand_field(base, field.index() as u64)
309 // We could get more projections by using e.g., `operand_projection`,
310 // but we do not even have the stack frame set up properly so
311 // an `Index` projection would throw us off-track.
315 PlaceBase::Static(box Static {kind: StaticKind::Promoted(promoted), ..})
317 let generics = self.tcx.generics_of(self.source.def_id());
318 if generics.requires_monomorphization(self.tcx) {
319 // FIXME: can't handle code with generics
322 let substs = InternalSubsts::identity_for_item(self.tcx, self.source.def_id());
323 let instance = Instance::new(self.source.def_id(), substs);
326 promoted: Some(promoted),
328 // cannot use `const_eval` here, because that would require having the MIR
329 // for the current function available, but we're producing said MIR right now
330 let res = self.use_ecx(source_info, |this| {
331 let mir = &this.promoted[promoted];
332 eval_promoted(this.tcx, cid, mir, this.param_env)
334 trace!("evaluated promoted {:?} to {:?}", promoted, res);
341 fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<Const<'tcx>> {
343 Operand::Constant(ref c) => self.eval_constant(c),
344 | Operand::Move(ref place)
345 | Operand::Copy(ref place) => self.eval_place(place, source_info),
351 rvalue: &Rvalue<'tcx>,
352 place_layout: TyLayout<'tcx>,
353 source_info: SourceInfo,
354 ) -> Option<Const<'tcx>> {
355 let span = source_info.span;
357 Rvalue::Use(ref op) => {
358 self.eval_operand(op, source_info)
362 Rvalue::Aggregate(..) |
363 Rvalue::NullaryOp(NullOp::Box, _) |
364 Rvalue::Discriminant(..) => None,
366 Rvalue::Cast(kind, ref operand, _) => {
367 let op = self.eval_operand(operand, source_info)?;
368 self.use_ecx(source_info, |this| {
369 let dest = this.ecx.allocate(place_layout, MemoryKind::Stack);
370 this.ecx.cast(op, kind, dest.into())?;
375 // FIXME(oli-obk): evaluate static/constant slice lengths
376 Rvalue::Len(_) => None,
377 Rvalue::NullaryOp(NullOp::SizeOf, ty) => {
378 type_size_of(self.tcx, self.param_env, ty).and_then(|n| Some(
380 imm: Immediate::Scalar(
383 size: self.tcx.data_layout.pointer_size.bytes() as u8,
386 layout: self.tcx.layout_of(self.param_env.and(self.tcx.types.usize)).ok()?,
390 Rvalue::UnaryOp(op, ref arg) => {
391 let def_id = if self.tcx.is_closure(self.source.def_id()) {
392 self.tcx.closure_base_def_id(self.source.def_id())
396 let generics = self.tcx.generics_of(def_id);
397 if generics.requires_monomorphization(self.tcx) {
398 // FIXME: can't handle code with generics
402 let arg = self.eval_operand(arg, source_info)?;
403 let val = self.use_ecx(source_info, |this| {
404 let prim = this.ecx.read_immediate(arg)?;
407 // Need to do overflow check here: For actual CTFE, MIR
408 // generation emits code that does this before calling the op.
409 if prim.to_bits()? == (1 << (prim.layout.size.bits() - 1)) {
410 return err!(OverflowNeg);
417 // Now run the actual operation.
418 this.ecx.unary_op(op, prim)
421 imm: Immediate::Scalar(val.into()),
422 layout: place_layout,
426 Rvalue::CheckedBinaryOp(op, ref left, ref right) |
427 Rvalue::BinaryOp(op, ref left, ref right) => {
428 trace!("rvalue binop {:?} for {:?} and {:?}", op, left, right);
429 let right = self.eval_operand(right, source_info)?;
430 let def_id = if self.tcx.is_closure(self.source.def_id()) {
431 self.tcx.closure_base_def_id(self.source.def_id())
435 let generics = self.tcx.generics_of(def_id);
436 if generics.requires_monomorphization(self.tcx) {
437 // FIXME: can't handle code with generics
441 let r = self.use_ecx(source_info, |this| {
442 this.ecx.read_immediate(right)
444 if op == BinOp::Shr || op == BinOp::Shl {
445 let left_ty = left.ty(&self.local_decls, self.tcx);
448 .layout_of(self.param_env.and(left_ty))
452 let right_size = right.layout.size;
453 let r_bits = r.to_scalar().and_then(|r| r.to_bits(right_size));
454 if r_bits.ok().map_or(false, |b| b >= left_bits as u128) {
455 let source_scope_local_data = match self.source_scope_local_data {
456 ClearCrossCrate::Set(ref data) => data,
457 ClearCrossCrate::Clear => return None,
459 let dir = if op == BinOp::Shr {
464 let hir_id = source_scope_local_data[source_info.scope].lint_root;
466 ::rustc::lint::builtin::EXCEEDING_BITSHIFTS,
469 &format!("attempt to shift {} with overflow", dir));
473 let left = self.eval_operand(left, source_info)?;
474 let l = self.use_ecx(source_info, |this| {
475 this.ecx.read_immediate(left)
477 trace!("const evaluating {:?} for {:?} and {:?}", op, left, right);
478 let (val, overflow) = self.use_ecx(source_info, |this| {
479 this.ecx.binary_op(op, l, r)
481 let val = if let Rvalue::CheckedBinaryOp(..) = *rvalue {
482 Immediate::ScalarPair(
484 Scalar::from_bool(overflow).into(),
488 let err = InterpError::Overflow(op).into();
489 let _: Option<()> = self.use_ecx(source_info, |_| Err(err));
492 Immediate::Scalar(val.into())
496 layout: place_layout,
503 fn operand_from_scalar(&self, scalar: Scalar, ty: Ty<'tcx>, span: Span) -> Operand<'tcx> {
504 Operand::Constant(Box::new(
509 literal: self.tcx.mk_const(ty::Const::from_scalar(
517 fn replace_with_const(&self, rval: &mut Rvalue<'tcx>, value: Const<'tcx>, span: Span) {
518 self.ecx.validate_operand(
523 ).expect("value should already be a valid const");
525 if let interpret::Operand::Immediate(im) = *value {
527 interpret::Immediate::Scalar(ScalarMaybeUndef::Scalar(scalar)) => {
528 *rval = Rvalue::Use(self.operand_from_scalar(scalar, value.layout.ty, span));
530 Immediate::ScalarPair(
531 ScalarMaybeUndef::Scalar(one),
532 ScalarMaybeUndef::Scalar(two)
534 let ty = &value.layout.ty.sty;
535 if let ty::Tuple(substs) = ty {
536 *rval = Rvalue::Aggregate(
537 Box::new(AggregateKind::Tuple),
539 self.operand_from_scalar(one, substs[0].expect_ty(), span),
540 self.operand_from_scalar(two, substs[1].expect_ty(), span),
550 fn should_const_prop(&self) -> bool {
551 self.tcx.sess.opts.debugging_opts.mir_opt_level >= 2
555 fn type_size_of<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
556 param_env: ty::ParamEnv<'tcx>,
557 ty: Ty<'tcx>) -> Option<u64> {
558 tcx.layout_of(param_env.and(ty)).ok().map(|layout| layout.size.bytes())
561 struct CanConstProp {
562 can_const_prop: IndexVec<Local, bool>,
563 // false at the beginning, once set, there are not allowed to be any more assignments
564 found_assignment: IndexVec<Local, bool>,
568 /// returns true if `local` can be propagated
569 fn check(mir: &Mir<'_>) -> IndexVec<Local, bool> {
570 let mut cpv = CanConstProp {
571 can_const_prop: IndexVec::from_elem(true, &mir.local_decls),
572 found_assignment: IndexVec::from_elem(false, &mir.local_decls),
574 for (local, val) in cpv.can_const_prop.iter_enumerated_mut() {
575 // cannot use args at all
576 // cannot use locals because if x < y { y - x } else { x - y } would
578 // FIXME(oli-obk): lint variables until they are used in a condition
579 // FIXME(oli-obk): lint if return value is constant
580 *val = mir.local_kind(local) == LocalKind::Temp;
587 impl<'tcx> Visitor<'tcx> for CanConstProp {
591 context: PlaceContext,
594 use rustc::mir::visit::PlaceContext::*;
596 // Constants must have at most one write
597 // FIXME(oli-obk): we could be more powerful here, if the multiple writes
598 // only occur in independent execution paths
599 MutatingUse(MutatingUseContext::Store) => if self.found_assignment[local] {
600 self.can_const_prop[local] = false;
602 self.found_assignment[local] = true
604 // Reading constants is allowed an arbitrary number of times
605 NonMutatingUse(NonMutatingUseContext::Copy) |
606 NonMutatingUse(NonMutatingUseContext::Move) |
607 NonMutatingUse(NonMutatingUseContext::Inspect) |
608 NonMutatingUse(NonMutatingUseContext::Projection) |
609 MutatingUse(MutatingUseContext::Projection) |
611 _ => self.can_const_prop[local] = false,
616 impl<'b, 'a, 'tcx> MutVisitor<'tcx> for ConstPropagator<'b, 'a, 'tcx> {
619 constant: &mut Constant<'tcx>,
622 trace!("visit_constant: {:?}", constant);
623 self.super_constant(constant, location);
624 self.eval_constant(constant);
629 statement: &mut Statement<'tcx>,
632 trace!("visit_statement: {:?}", statement);
633 if let StatementKind::Assign(ref place, ref mut rval) = statement.kind {
634 let place_ty: Ty<'tcx> = place
635 .ty(&self.local_decls, self.tcx)
637 if let Ok(place_layout) = self.tcx.layout_of(self.param_env.and(place_ty)) {
638 if let Some(value) = self.const_prop(rval, place_layout, statement.source_info) {
639 if let Place::Base(PlaceBase::Local(local)) = *place {
640 trace!("checking whether {:?} can be stored to {:?}", value, local);
641 if self.can_const_prop[local] {
642 trace!("storing {:?} to {:?}", value, local);
643 assert!(self.places[local].is_none());
644 self.places[local] = Some(value);
646 if self.should_const_prop() {
647 self.replace_with_const(rval, value, statement.source_info.span);
654 self.super_statement(statement, location);
659 terminator: &mut Terminator<'tcx>,
662 self.super_terminator(terminator, location);
663 let source_info = terminator.source_info;
664 match &mut terminator.kind {
665 TerminatorKind::Assert { expected, msg, ref mut cond, .. } => {
666 if let Some(value) = self.eval_operand(&cond, source_info) {
667 trace!("assertion on {:?} should be {:?}", value, expected);
668 let expected = ScalarMaybeUndef::from(Scalar::from_bool(*expected));
669 let value_const = self.ecx.read_scalar(value).unwrap();
670 if expected != value_const {
671 // poison all places this operand references so that further code
672 // doesn't use the invalid value
674 Operand::Move(ref place) | Operand::Copy(ref place) => {
675 let mut place = place;
676 while let Place::Projection(ref proj) = *place {
679 if let Place::Base(PlaceBase::Local(local)) = *place {
680 self.places[local] = None;
683 Operand::Constant(_) => {}
685 let span = terminator.source_info.span;
689 .as_local_hir_id(self.source.def_id())
690 .expect("some part of a failing const eval must be local");
691 use rustc::mir::interpret::InterpError::*;
692 let msg = match msg {
696 RemainderByZero => msg.description().to_owned(),
697 BoundsCheck { ref len, ref index } => {
699 .eval_operand(len, source_info)
700 .expect("len must be const");
701 let len = match self.ecx.read_scalar(len) {
702 Ok(ScalarMaybeUndef::Scalar(Scalar::Bits {
705 other => bug!("const len not primitive: {:?}", other),
708 .eval_operand(index, source_info)
709 .expect("index must be const");
710 let index = match self.ecx.read_scalar(index) {
711 Ok(ScalarMaybeUndef::Scalar(Scalar::Bits {
714 other => bug!("const index not primitive: {:?}", other),
717 "index out of bounds: \
718 the len is {} but the index is {}",
723 // Need proper const propagator for these
727 ::rustc::lint::builtin::CONST_ERR,
733 if self.should_const_prop() {
734 if let ScalarMaybeUndef::Scalar(scalar) = value_const {
735 *cond = self.operand_from_scalar(
745 TerminatorKind::SwitchInt { ref mut discr, switch_ty, .. } => {
746 if self.should_const_prop() {
747 if let Some(value) = self.eval_operand(&discr, source_info) {
748 if let ScalarMaybeUndef::Scalar(scalar) =
749 self.ecx.read_scalar(value).unwrap() {
750 *discr = self.operand_from_scalar(scalar, switch_ty, source_info.span);
755 //none of these have Operands to const-propagate
756 TerminatorKind::Goto { .. } |
757 TerminatorKind::Resume |
758 TerminatorKind::Abort |
759 TerminatorKind::Return |
760 TerminatorKind::Unreachable |
761 TerminatorKind::Drop { .. } |
762 TerminatorKind::DropAndReplace { .. } |
763 TerminatorKind::Yield { .. } |
764 TerminatorKind::GeneratorDrop |
765 TerminatorKind::FalseEdges { .. } |
766 TerminatorKind::FalseUnwind { .. } => { }
767 //FIXME(wesleywiser) Call does have Operands that could be const-propagated
768 TerminatorKind::Call { .. } => { }