Skip to content

Commit b47c314

Browse files
committed
Auto merge of #52991 - nikomatsakis:nll-escaping-into-return, r=pnkfelix
avoid computing liveness for locals that escape into statics Fixes #52713 I poked at this on the plane and I think it's working -- but I want to do a bit more investigation and double check. The idea is to identify those local variables where the entire value will "escape" into the return -- for them, we don't need to compute liveness, since we know that the outlives relations from the return type will force those regions to be equal to free regions. This should help with html5ever in particular. - [x] test performance - [x] verify correctness - [x] add comments r? @pnkfelix cc @lqd
2 parents ddcf17e + 2e2ea26 commit b47c314

File tree

9 files changed

+331
-65
lines changed

9 files changed

+331
-65
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! Identify those variables whose entire value will eventually be
12+
//! returned from the fn via the RETURN_PLACE. As an optimization, we
13+
//! can skip computing liveness results for those variables. The idea
14+
//! is that the return type of the fn only ever contains free
15+
//! regions. Therefore, the types of those variables are going to
16+
//! ultimately be contrained to outlive those free regions -- since
17+
//! free regions are always live for the entire body, this implies
18+
//! that the liveness results are not important for those regions.
19+
//! This is most important in the "fns" that we create to represent static
20+
//! values, since those are often really quite large, and all regions in them
21+
//! will ultimately be constrained to be `'static`. Two examples:
22+
//!
23+
//! ```
24+
//! fn foo() -> &'static [u32] { &[] }
25+
//! static FOO: &[u32] = &[];
26+
//! ```
27+
//!
28+
//! In both these cases, the return value will only have static lifetime.
29+
//!
30+
//! NB: The simple logic here relies on the fact that outlives
31+
//! relations in our analysis don't have locations. Otherwise, we
32+
//! would have to restrict ourselves to values that are
33+
//! *unconditionally* returned (which would still cover the "big
34+
//! static value" case).
35+
//!
36+
//! The way that this code works is to use union-find -- we iterate
37+
//! over the MIR and union together two variables X and Y if all
38+
//! regions in the value of Y are going to be stored into X -- that
39+
//! is, if `typeof(X): 'a` requires that `typeof(Y): 'a`. This means
40+
//! that e.g. we can union together `x` and `y` if we have something
41+
//! like `x = (y, 22)`, but not something like `x = y.f` (since there
42+
//! may be regions in the type of `y` that do not appear in the field
43+
//! `f`).
44+
45+
use rustc::mir::visit::Visitor;
46+
use rustc::mir::*;
47+
48+
use rustc_data_structures::indexed_vec::Idx;
49+
use rustc_data_structures::unify as ut;
50+
51+
crate struct EscapingLocals {
52+
unification_table: ut::UnificationTable<ut::InPlace<AssignedLocal>>,
53+
}
54+
55+
impl EscapingLocals {
56+
crate fn compute(mir: &Mir<'tcx>) -> Self {
57+
let mut visitor = GatherAssignedLocalsVisitor::new();
58+
visitor.visit_mir(mir);
59+
60+
EscapingLocals {
61+
unification_table: visitor.unification_table,
62+
}
63+
}
64+
65+
/// True if `local` is known to escape into static
66+
/// memory.
67+
crate fn escapes_into_return(&mut self, local: Local) -> bool {
68+
let return_place = AssignedLocal::from(RETURN_PLACE);
69+
let other_place = AssignedLocal::from(local);
70+
self.unification_table.unioned(return_place, other_place)
71+
}
72+
}
73+
74+
/// The MIR visitor gathering the union-find of the locals used in
75+
/// assignments.
76+
struct GatherAssignedLocalsVisitor {
77+
unification_table: ut::UnificationTable<ut::InPlace<AssignedLocal>>,
78+
}
79+
80+
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
81+
struct AssignedLocal(u32);
82+
83+
impl ut::UnifyKey for AssignedLocal {
84+
type Value = ();
85+
86+
fn index(&self) -> u32 {
87+
self.0
88+
}
89+
90+
fn from_index(i: u32) -> AssignedLocal {
91+
AssignedLocal(i)
92+
}
93+
94+
fn tag() -> &'static str {
95+
"AssignedLocal"
96+
}
97+
}
98+
99+
impl From<Local> for AssignedLocal {
100+
fn from(item: Local) -> Self {
101+
// newtype_indexes use usize but are u32s.
102+
assert!(item.index() < ::std::u32::MAX as usize);
103+
AssignedLocal(item.index() as u32)
104+
}
105+
}
106+
107+
impl GatherAssignedLocalsVisitor {
108+
fn new() -> Self {
109+
Self {
110+
unification_table: ut::UnificationTable::new(),
111+
}
112+
}
113+
114+
fn union_locals_if_needed(&mut self, lvalue: Option<Local>, rvalue: Option<Local>) {
115+
if let Some(lvalue) = lvalue {
116+
if let Some(rvalue) = rvalue {
117+
if lvalue != rvalue {
118+
debug!("EscapingLocals: union {:?} and {:?}", lvalue, rvalue);
119+
self.unification_table
120+
.union(AssignedLocal::from(lvalue), AssignedLocal::from(rvalue));
121+
}
122+
}
123+
}
124+
}
125+
}
126+
127+
// Returns the potential `Local` associated to this `Place` or `PlaceProjection`
128+
fn find_local_in_place(place: &Place) -> Option<Local> {
129+
match place {
130+
Place::Local(local) => Some(*local),
131+
132+
// If you do e.g. `x = a.f` then only *part* of the type of
133+
// `a` escapes into `x` (the part contained in `f`); if `a`'s
134+
// type has regions that don't appear in `f`, those might not
135+
// escape.
136+
Place::Projection(..) => None,
137+
138+
Place::Static { .. } | Place::Promoted { .. } => None,
139+
}
140+
}
141+
142+
// Returns the potential `Local` in this `Operand`.
143+
fn find_local_in_operand(op: &Operand) -> Option<Local> {
144+
// Conservatively check a subset of `Operand`s we know our
145+
// benchmarks track, for example `html5ever`.
146+
match op {
147+
Operand::Copy(place) | Operand::Move(place) => find_local_in_place(place),
148+
Operand::Constant(_) => None,
149+
}
150+
}
151+
152+
impl Visitor<'tcx> for GatherAssignedLocalsVisitor {
153+
fn visit_mir(&mut self, mir: &Mir<'tcx>) {
154+
// We need as many union-find keys as there are locals
155+
for _ in 0..mir.local_decls.len() {
156+
self.unification_table.new_key(());
157+
}
158+
159+
self.super_mir(mir);
160+
}
161+
162+
fn visit_assign(
163+
&mut self,
164+
block: BasicBlock,
165+
place: &Place<'tcx>,
166+
rvalue: &Rvalue<'tcx>,
167+
location: Location,
168+
) {
169+
let local = find_local_in_place(place);
170+
171+
// Find those cases where there is a `Place` consumed by
172+
// `rvalue` and we know that all regions in its type will be
173+
// incorporated into `place`, the `Place` we are assigning to.
174+
match rvalue {
175+
// `x = y` is the simplest possible case.
176+
Rvalue::Use(op) => self.union_locals_if_needed(local, find_local_in_operand(op)),
177+
178+
// `X = &'r P` -- the type of `X` will be `&'r T_P`, where
179+
// `T_P` is the type of `P`.
180+
Rvalue::Ref(_, _, place) => {
181+
// Special case: if you have `X = &*Y` (or `X = &**Y`
182+
// etc), then the outlives relationships will ensure
183+
// that all regions in `Y` are constrained by regions
184+
// in `X` -- this is because the lifetimes of the
185+
// references we deref through are required to outlive
186+
// the borrow lifetime `'r` (which appears in `X`).
187+
//
188+
// (We don't actually need to check the type of `Y`:
189+
// since `ProjectionElem::Deref` represents a built-in
190+
// deref and not an overloaded deref, if the thing we
191+
// deref through is not a reference, then it must be a
192+
// `Box` or `*const`, in which case it contains no
193+
// references.)
194+
let mut place_ref = place;
195+
while let Place::Projection(proj) = place_ref {
196+
if let ProjectionElem::Deref = proj.elem {
197+
place_ref = &proj.base;
198+
} else {
199+
break;
200+
}
201+
}
202+
203+
self.union_locals_if_needed(local, find_local_in_place(place_ref))
204+
}
205+
206+
Rvalue::Cast(kind, op, _) => match kind {
207+
CastKind::Unsize => {
208+
// Casting a `&[T; N]` to `&[T]` or `&Foo` to `&Trait` --
209+
// in both cases, no regions are "lost".
210+
self.union_locals_if_needed(local, find_local_in_operand(op))
211+
}
212+
_ => (),
213+
},
214+
215+
// Constructing an aggregate like `(x,)` or `Foo { x }`
216+
// includes the full type of `x`.
217+
Rvalue::Aggregate(_, ops) => {
218+
for rvalue in ops.iter().map(find_local_in_operand) {
219+
self.union_locals_if_needed(local, rvalue);
220+
}
221+
}
222+
223+
// For other things, be conservative and do not union.
224+
_ => (),
225+
};
226+
227+
self.super_assign(block, place, rvalue, location);
228+
}
229+
}

src/librustc_mir/borrow_check/nll/liveness_map.rs

+35-13
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
//! liveness code so that it only operates over variables with regions in their
1717
//! types, instead of all variables.
1818
19+
use borrow_check::nll::escaping_locals::EscapingLocals;
20+
use rustc::mir::{Local, Mir};
1921
use rustc::ty::TypeFoldable;
2022
use rustc_data_structures::indexed_vec::IndexVec;
21-
use rustc::mir::{Mir, Local};
2223
use util::liveness::LiveVariableMap;
2324

2425
use rustc_data_structures::indexed_vec::Idx;
@@ -29,14 +30,13 @@ use rustc_data_structures::indexed_vec::Idx;
2930
crate struct NllLivenessMap {
3031
/// For each local variable, contains either None (if the type has no regions)
3132
/// or Some(i) with a suitable index.
32-
pub from_local: IndexVec<Local, Option<LocalWithRegion>>,
33-
/// For each LocalWithRegion, maps back to the original Local index.
34-
pub to_local: IndexVec<LocalWithRegion, Local>,
33+
from_local: IndexVec<Local, Option<LocalWithRegion>>,
3534

35+
/// For each LocalWithRegion, maps back to the original Local index.
36+
to_local: IndexVec<LocalWithRegion, Local>,
3637
}
3738

3839
impl LiveVariableMap for NllLivenessMap {
39-
4040
fn from_local(&self, local: Local) -> Option<Self::LiveVar> {
4141
self.from_local[local]
4242
}
@@ -55,21 +55,43 @@ impl LiveVariableMap for NllLivenessMap {
5555
impl NllLivenessMap {
5656
/// Iterates over the variables in Mir and assigns each Local whose type contains
5757
/// regions a LocalWithRegion index. Returns a map for converting back and forth.
58-
pub fn compute(mir: &Mir) -> Self {
58+
crate fn compute(mir: &Mir<'tcx>) -> Self {
59+
let mut escaping_locals = EscapingLocals::compute(mir);
60+
5961
let mut to_local = IndexVec::default();
60-
let from_local: IndexVec<Local,Option<_>> = mir
62+
let mut escapes_into_return = 0;
63+
let mut no_regions = 0;
64+
let from_local: IndexVec<Local, Option<_>> = mir
6165
.local_decls
6266
.iter_enumerated()
6367
.map(|(local, local_decl)| {
64-
if local_decl.ty.has_free_regions() {
65-
Some(to_local.push(local))
68+
if escaping_locals.escapes_into_return(local) {
69+
// If the local escapes into the return value,
70+
// then the return value will force all of the
71+
// regions in its type to outlive free regions
72+
// (e.g., `'static`) and hence liveness is not
73+
// needed. This is particularly important for big
74+
// statics.
75+
escapes_into_return += 1;
76+
None
77+
} else if local_decl.ty.has_free_regions() {
78+
let l = to_local.push(local);
79+
debug!("liveness_map: {:?} = {:?}", local, l);
80+
Some(l)
81+
} else {
82+
no_regions += 1;
83+
None
6684
}
67-
else {
68-
None
69-
}
7085
}).collect();
7186

72-
Self { from_local, to_local }
87+
debug!("liveness_map: {} variables need liveness", to_local.len());
88+
debug!("liveness_map: {} escapes into return", escapes_into_return);
89+
debug!("liveness_map: {} no regions", no_regions);
90+
91+
Self {
92+
from_local,
93+
to_local,
94+
}
7395
}
7496
}
7597

src/librustc_mir/borrow_check/nll/mod.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ use polonius_engine::{Algorithm, Output};
3939
use util as mir_util;
4040
use util::pretty::{self, ALIGN};
4141

42+
mod constraints;
4243
mod constraint_generation;
44+
mod escaping_locals;
4345
pub mod explain_borrow;
4446
mod facts;
4547
mod invalidation;
@@ -49,8 +51,6 @@ crate mod type_check;
4951
mod universal_regions;
5052
crate mod liveness_map;
5153

52-
mod constraints;
53-
5454
use self::facts::AllFacts;
5555
use self::region_infer::RegionInferenceContext;
5656
use self::universal_regions::UniversalRegions;
@@ -120,6 +120,7 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
120120
location_table,
121121
borrow_set,
122122
&liveness,
123+
&liveness_map,
123124
&mut all_facts,
124125
flow_inits,
125126
move_data,
@@ -205,6 +206,7 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
205206
dump_mir_results(
206207
infcx,
207208
&liveness,
209+
&liveness_map,
208210
MirSource::item(def_id),
209211
&mir,
210212
&regioncx,
@@ -221,6 +223,7 @@ pub(in borrow_check) fn compute_regions<'cx, 'gcx, 'tcx>(
221223
fn dump_mir_results<'a, 'gcx, 'tcx>(
222224
infcx: &InferCtxt<'a, 'gcx, 'tcx>,
223225
liveness: &LivenessResults<LocalWithRegion>,
226+
liveness_map: &NllLivenessMap,
224227
source: MirSource,
225228
mir: &Mir<'tcx>,
226229
regioncx: &RegionInferenceContext,
@@ -230,16 +233,14 @@ fn dump_mir_results<'a, 'gcx, 'tcx>(
230233
return;
231234
}
232235

233-
let map = &NllLivenessMap::compute(mir);
234-
235236
let regular_liveness_per_location: FxHashMap<_, _> = mir
236237
.basic_blocks()
237238
.indices()
238239
.flat_map(|bb| {
239240
let mut results = vec![];
240241
liveness
241242
.regular
242-
.simulate_block(&mir, bb, map, |location, local_set| {
243+
.simulate_block(&mir, bb, liveness_map, |location, local_set| {
243244
results.push((location, local_set.clone()));
244245
});
245246
results
@@ -253,7 +254,7 @@ fn dump_mir_results<'a, 'gcx, 'tcx>(
253254
let mut results = vec![];
254255
liveness
255256
.drop
256-
.simulate_block(&mir, bb, map, |location, local_set| {
257+
.simulate_block(&mir, bb, liveness_map, |location, local_set| {
257258
results.push((location, local_set.clone()));
258259
});
259260
results

0 commit comments

Comments
 (0)