rustc_mir_build/build/expr/
as_temp.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! See docs in build/expr/mod.rs

use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_hir::HirId;
use rustc_middle::middle::region;
use rustc_middle::middle::region::{Scope, ScopeData};
use rustc_middle::mir::*;
use rustc_middle::thir::*;
use tracing::{debug, instrument};

use crate::build::scope::DropKind;
use crate::build::{BlockAnd, BlockAndExtension, Builder};

impl<'a, 'tcx> Builder<'a, 'tcx> {
    /// Compile `expr` into a fresh temporary. This is used when building
    /// up rvalues so as to freeze the value that will be consumed.
    pub(crate) fn as_temp(
        &mut self,
        block: BasicBlock,
        temp_lifetime: Option<region::Scope>,
        expr_id: ExprId,
        mutability: Mutability,
    ) -> BlockAnd<Local> {
        // this is the only place in mir building that we need to truly need to worry about
        // infinite recursion. Everything else does recurse, too, but it always gets broken up
        // at some point by inserting an intermediate temporary
        ensure_sufficient_stack(|| self.as_temp_inner(block, temp_lifetime, expr_id, mutability))
    }

    #[instrument(skip(self), level = "debug")]
    fn as_temp_inner(
        &mut self,
        mut block: BasicBlock,
        temp_lifetime: Option<region::Scope>,
        expr_id: ExprId,
        mutability: Mutability,
    ) -> BlockAnd<Local> {
        let this = self;

        let expr = &this.thir[expr_id];
        let expr_span = expr.span;
        let source_info = this.source_info(expr_span);
        if let ExprKind::Scope { region_scope, lint_level, value } = expr.kind {
            return this.in_scope((region_scope, source_info), lint_level, |this| {
                this.as_temp(block, temp_lifetime, value, mutability)
            });
        }

        let expr_ty = expr.ty;
        let deduplicate_temps =
            this.fixed_temps_scope.is_some() && this.fixed_temps_scope == temp_lifetime;
        let temp = if deduplicate_temps && let Some(temp_index) = this.fixed_temps.get(&expr_id) {
            *temp_index
        } else {
            let mut local_decl = LocalDecl::new(expr_ty, expr_span);
            if mutability.is_not() {
                local_decl = local_decl.immutable();
            }

            debug!("creating temp {:?} with block_context: {:?}", local_decl, this.block_context);
            let local_info = match expr.kind {
                ExprKind::StaticRef { def_id, .. } => {
                    assert!(!this.tcx.is_thread_local_static(def_id));
                    LocalInfo::StaticRef { def_id, is_thread_local: false }
                }
                ExprKind::ThreadLocalRef(def_id) => {
                    assert!(this.tcx.is_thread_local_static(def_id));
                    LocalInfo::StaticRef { def_id, is_thread_local: true }
                }
                ExprKind::NamedConst { def_id, .. } | ExprKind::ConstParam { def_id, .. } => {
                    LocalInfo::ConstRef { def_id }
                }
                // Find out whether this temp is being created within the
                // tail expression of a block whose result is ignored.
                _ if let Some(tail_info) = this.block_context.currently_in_block_tail() => {
                    LocalInfo::BlockTailTemp(tail_info)
                }

                _ if let Some(Scope { data: ScopeData::IfThenRescope, id }) = temp_lifetime => {
                    LocalInfo::IfThenRescopeTemp {
                        if_then: HirId { owner: this.hir_id.owner, local_id: id },
                    }
                }

                _ => LocalInfo::Boring,
            };
            **local_decl.local_info.as_mut().assert_crate_local() = local_info;
            this.local_decls.push(local_decl)
        };
        debug!(?temp);
        if deduplicate_temps {
            this.fixed_temps.insert(expr_id, temp);
        }
        let temp_place = Place::from(temp);

        match expr.kind {
            // Don't bother with StorageLive and Dead for these temporaries,
            // they are never assigned.
            ExprKind::Break { .. } | ExprKind::Continue { .. } | ExprKind::Return { .. } => (),
            ExprKind::Block { block }
                if let Block { expr: None, targeted_by_break: false, .. } = this.thir[block]
                    && expr_ty.is_never() => {}
            _ => {
                this.cfg
                    .push(block, Statement { source_info, kind: StatementKind::StorageLive(temp) });

                // In constants, `temp_lifetime` is `None` for temporaries that
                // live for the `'static` lifetime. Thus we do not drop these
                // temporaries and simply leak them.
                // This is equivalent to what `let x = &foo();` does in
                // functions. The temporary is lifted to their surrounding
                // scope. In a function that means the temporary lives until
                // just before the function returns. In constants that means it
                // outlives the constant's initialization value computation.
                // Anything outliving a constant must have the `'static`
                // lifetime and live forever.
                // Anything with a shorter lifetime (e.g the `&foo()` in
                // `bar(&foo())` or anything within a block will keep the
                // regular drops just like runtime code.
                if let Some(temp_lifetime) = temp_lifetime {
                    this.schedule_drop(expr_span, temp_lifetime, temp, DropKind::Storage);
                }
            }
        }

        block = this.expr_into_dest(temp_place, block, expr_id).into_block();

        if let Some(temp_lifetime) = temp_lifetime {
            this.schedule_drop(expr_span, temp_lifetime, temp, DropKind::Value);
        }

        block.and(temp)
    }
}