Finally start down the right path

This commit is contained in:
bobtwinkles 2018-03-05 02:44:10 -05:00
parent 580467d306
commit 138365368a
9 changed files with 427 additions and 431 deletions

View file

@ -18,7 +18,7 @@ use rustc_data_structures::sync::Lrc;
use super::{Context, MirBorrowckCtxt};
use super::{InitializationRequiringAction, PrefixSet};
use dataflow::{ActiveBorrows, BorrowData, FlowAtLocation, MovingOutStatements};
use dataflow::{Borrows, BorrowData, FlowAtLocation, MovingOutStatements};
use dataflow::move_paths::MovePathIndex;
use util::borrowck_errors::{BorrowckErrors, Origin};
@ -372,10 +372,10 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
context: Context,
borrow: &BorrowData<'tcx>,
drop_span: Span,
borrows: &ActiveBorrows<'cx, 'gcx, 'tcx>,
borrows: &Borrows<'cx, 'gcx, 'tcx>
) {
let end_span = borrows.opt_region_end_span(&borrow.region);
let scope_tree = borrows.0.scope_tree();
let scope_tree = borrows.scope_tree();
let root_place = self.prefixes(&borrow.borrowed_place, PrefixSet::All)
.last()
.unwrap();

View file

@ -17,13 +17,14 @@ use rustc::mir::{BasicBlock, Location};
use dataflow::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
use dataflow::{EverInitializedPlaces, MovingOutStatements};
use dataflow::{ActiveBorrows, FlowAtLocation, FlowsAtLocation};
use dataflow::{Borrows};
use dataflow::{FlowAtLocation, FlowsAtLocation};
use dataflow::move_paths::HasMoveData;
use std::fmt;
// (forced to be `pub` due to its use as an associated type below.)
pub(crate) struct Flows<'b, 'gcx: 'tcx, 'tcx: 'b> {
pub borrows: FlowAtLocation<ActiveBorrows<'b, 'gcx, 'tcx>>,
pub borrows: FlowAtLocation<Borrows<'b, 'gcx, 'tcx>>,
pub inits: FlowAtLocation<MaybeInitializedPlaces<'b, 'gcx, 'tcx>>,
pub uninits: FlowAtLocation<MaybeUninitializedPlaces<'b, 'gcx, 'tcx>>,
pub move_outs: FlowAtLocation<MovingOutStatements<'b, 'gcx, 'tcx>>,
@ -32,7 +33,7 @@ pub(crate) struct Flows<'b, 'gcx: 'tcx, 'tcx: 'b> {
impl<'b, 'gcx, 'tcx> Flows<'b, 'gcx, 'tcx> {
pub fn new(
borrows: FlowAtLocation<ActiveBorrows<'b, 'gcx, 'tcx>>,
borrows: FlowAtLocation<Borrows<'b, 'gcx, 'tcx>>,
inits: FlowAtLocation<MaybeInitializedPlaces<'b, 'gcx, 'tcx>>,
uninits: FlowAtLocation<MaybeUninitializedPlaces<'b, 'gcx, 'tcx>>,
move_outs: FlowAtLocation<MovingOutStatements<'b, 'gcx, 'tcx>>,

View file

@ -34,11 +34,10 @@ use syntax_pos::Span;
use dataflow::{do_dataflow, DebugFormatted};
use dataflow::FlowAtLocation;
use dataflow::MoveDataParamEnv;
use dataflow::{DataflowAnalysis, DataflowResultsConsumer};
use dataflow::{DataflowResultsConsumer};
use dataflow::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
use dataflow::{EverInitializedPlaces, MovingOutStatements};
use dataflow::{BorrowData, Borrows, ReserveOrActivateIndex};
use dataflow::{ActiveBorrows, Reservations};
use dataflow::indexes::BorrowIndex;
use dataflow::move_paths::{IllegalMoveOriginKind, MoveError};
use dataflow::move_paths::{HasMoveData, LookupResult, MoveData, MovePathIndex};
@ -54,8 +53,6 @@ mod error_reporting;
mod flows;
mod prefixes;
use std::borrow::Cow;
pub(crate) mod nll;
pub fn provide(providers: &mut Providers) {
@ -209,6 +206,18 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>(
};
let flow_inits = flow_inits; // remove mut
let flow_borrows = FlowAtLocation::new(do_dataflow(
tcx,
mir,
id,
&attributes,
&dead_unwinds,
Borrows::new(tcx, mir, opt_regioncx.clone(), def_id, body_id),
|rs, i| {
DebugFormatted::new(&(i.kind(), rs.location(i.borrow_index())))
}
));
let movable_generator = !match tcx.hir.get(id) {
hir::map::Node::NodeExpr(&hir::Expr {
node: hir::ExprClosure(.., Some(hir::GeneratorMovability::Static)),
@ -230,44 +239,12 @@ fn do_mir_borrowck<'a, 'gcx, 'tcx>(
},
access_place_error_reported: FxHashSet(),
reservation_error_reported: FxHashSet(),
nonlexical_regioncx: opt_regioncx.clone(),
nonlexical_regioncx: opt_regioncx,
nonlexical_cause_info: None,
};
let borrows = Borrows::new(tcx, mir, opt_regioncx, def_id, body_id);
let flow_reservations = do_dataflow(
tcx,
mir,
id,
&attributes,
&dead_unwinds,
Reservations::new(borrows),
|rs, i| {
// In principle we could make the dataflow ensure that
// only reservation bits show up, and assert so here.
//
// In practice it is easier to be looser; in particular,
// it is okay for the kill-sets to hold activation bits.
DebugFormatted::new(&(i.kind(), rs.location(i)))
},
);
let flow_active_borrows = {
let reservations_on_entry = flow_reservations.0.sets.entry_set_state();
let reservations = flow_reservations.0.operator;
let a = DataflowAnalysis::new_with_entry_sets(
mir,
&dead_unwinds,
Cow::Borrowed(reservations_on_entry),
ActiveBorrows::new(reservations),
);
let results = a.run(tcx, id, &attributes, |ab, i| {
DebugFormatted::new(&(i.kind(), ab.location(i)))
});
FlowAtLocation::new(results)
};
let mut state = Flows::new(
flow_active_borrows,
flow_borrows,
flow_inits,
flow_uninits,
flow_move_outs,

View file

@ -63,48 +63,27 @@ pub struct Borrows<'a, 'gcx: 'tcx, 'tcx: 'a> {
/// to its borrow-indexes.
assigned_map: FxHashMap<Place<'tcx>, FxHashSet<BorrowIndex>>,
/// Locations which activate borrows.
/// NOTE: A given location may activate more than one borrow in the future
/// when more general two-phase borrow support is introduced, but for now we
/// only need to store one borrow index
activation_map: FxHashMap<Location, BorrowIndex>,
/// Every borrow has a region; this maps each such regions back to
/// its borrow-indexes.
region_map: FxHashMap<Region<'tcx>, FxHashSet<BorrowIndex>>,
/// Map from local to all the borrows on that local
local_map: FxHashMap<mir::Local, FxHashSet<BorrowIndex>>,
/// Maps regions to their corresponding source spans
/// Only contains ReScope()s as keys
region_span_map: FxHashMap<RegionKind, Span>,
/// NLL region inference context with which NLL queries should be resolved
nonlexical_regioncx: Option<Rc<RegionInferenceContext<'tcx>>>,
}
// Two-phase borrows actually requires two flow analyses; they need
// to be separate because the final results of the first are used to
// construct the gen+kill sets for the second. (The dataflow system
// is not designed to allow the gen/kill sets to change during the
// fixed-point iteration.)
/// The `Reservations` analysis is the first of the two flow analyses
/// tracking (phased) borrows. It computes where a borrow is reserved;
/// i.e. where it can reach in the control flow starting from its
/// initial `assigned = &'rgn borrowed` statement, and ending
/// wherever `'rgn` itself ends.
pub(crate) struct Reservations<'a, 'gcx: 'tcx, 'tcx: 'a>(pub(crate) Borrows<'a, 'gcx, 'tcx>);
/// The `ActiveBorrows` analysis is the second of the two flow
/// analyses tracking (phased) borrows. It computes where any given
/// borrow `&assigned = &'rgn borrowed` is *active*, which starts at
/// the first use of `assigned` after the reservation has started, and
/// ends wherever `'rgn` itself ends.
pub(crate) struct ActiveBorrows<'a, 'gcx: 'tcx, 'tcx: 'a>(pub(crate) Borrows<'a, 'gcx, 'tcx>);
impl<'a, 'gcx, 'tcx> Reservations<'a, 'gcx, 'tcx> {
pub(crate) fn new(b: Borrows<'a, 'gcx, 'tcx>) -> Self { Reservations(b) }
pub(crate) fn location(&self, idx: ReserveOrActivateIndex) -> &Location {
self.0.location(idx.borrow_index())
}
}
impl<'a, 'gcx, 'tcx> ActiveBorrows<'a, 'gcx, 'tcx> {
pub(crate) fn new(r: Reservations<'a, 'gcx, 'tcx>) -> Self { ActiveBorrows(r.0) }
pub(crate) fn location(&self, idx: ReserveOrActivateIndex) -> &Location {
self.0.location(idx.borrow_index())
}
}
// temporarily allow some dead fields: `kind` and `region` will be
// needed by borrowck; `borrowed_place` will probably be a MovePathIndex when
// that is extended to include borrowed data paths.
@ -114,9 +93,15 @@ pub struct BorrowData<'tcx> {
/// Location where the borrow reservation starts.
/// In many cases, this will be equal to the activation location but not always.
pub(crate) reserve_location: Location,
/// Point where the borrow is activated.
pub(crate) activate_location: Location,
/// What kind of borrow this is
pub(crate) kind: mir::BorrowKind,
/// The region for which this borrow is live
pub(crate) region: Region<'tcx>,
/// Place from which we are borrowing
pub(crate) borrowed_place: mir::Place<'tcx>,
/// Place to which the borrow was stored
pub(crate) assigned_place: mir::Place<'tcx>,
}
@ -165,9 +150,11 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
idx_vec: IndexVec::new(),
location_map: FxHashMap(),
assigned_map: FxHashMap(),
activation_map: FxHashMap(),
region_map: FxHashMap(),
local_map: FxHashMap(),
region_span_map: FxHashMap()
region_span_map: FxHashMap(),
nonlexical_regioncx: nonlexical_regioncx.clone()
};
visitor.visit_mir(mir);
return Borrows { tcx: tcx,
@ -177,6 +164,7 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
root_scope,
location_map: visitor.location_map,
assigned_map: visitor.assigned_map,
activation_map: visitor.activation_map,
region_map: visitor.region_map,
local_map: visitor.local_map,
region_span_map: visitor.region_span_map,
@ -188,9 +176,11 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
idx_vec: IndexVec<BorrowIndex, BorrowData<'tcx>>,
location_map: FxHashMap<Location, BorrowIndex>,
assigned_map: FxHashMap<Place<'tcx>, FxHashSet<BorrowIndex>>,
activation_map: FxHashMap<Location, BorrowIndex>,
region_map: FxHashMap<Region<'tcx>, FxHashSet<BorrowIndex>>,
local_map: FxHashMap<mir::Local, FxHashSet<BorrowIndex>>,
region_span_map: FxHashMap<RegionKind, Span>,
nonlexical_regioncx: Option<Rc<RegionInferenceContext<'tcx>>>,
}
impl<'a, 'gcx, 'tcx> Visitor<'tcx> for GatherBorrows<'a, 'gcx, 'tcx> {
@ -210,15 +200,25 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
if let mir::Rvalue::Ref(region, kind, ref borrowed_place) = *rvalue {
if is_unsafe_place(self.tcx, self.mir, borrowed_place) { return; }
let activate_location = self.compute_activation_location(location,
&assigned_place,
region,
kind);
let borrow = BorrowData {
activate_location, kind, region,
reserve_location: location,
kind, region,
borrowed_place: borrowed_place.clone(),
assigned_place: assigned_place.clone(),
};
let idx = self.idx_vec.push(borrow);
self.location_map.insert(location, idx);
// This assert is a good sanity check until more general 2-phase borrow
// support is introduced. See NOTE on the activation_map field for more
assert!(!self.activation_map.contains_key(&activate_location),
"More than one activation introduced at the same location.");
self.activation_map.insert(activate_location, idx);
insert(&mut self.assigned_map, assigned_place, idx);
insert(&mut self.region_map, &region, idx);
if let Some(local) = root_local(borrowed_place) {
@ -273,6 +273,246 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
return self.super_statement(block, statement, location);
}
}
/// Represents what kind of usage we've seen.
enum PlaceUsageType {
/// No usage seen
None,
/// Has been seen as the argument to a StorageDead statement. This is required to
/// gracefully handle cases where user code has an unneeded
StorageKilled,
/// Has been used in borrow-activating context
BorrowActivateUsage
}
/// A MIR visitor that determines if a specific place is used in a two-phase activating
/// manner in a given chunk of MIR.
struct ContainsUseOfPlace<'b, 'tcx: 'b> {
target: &'b Place<'tcx>,
use_found: bool,
}
impl<'b, 'tcx: 'b> ContainsUseOfPlace<'b, 'tcx> {
fn new(place: &'b Place<'tcx>) -> Self {
Self { target: place, use_found: false }
}
/// return whether `context` should be considered a "use" of a
/// place found in that context. "Uses" activate associated
/// borrows (at least when such uses occur while the borrow also
/// has a reservation at the time).
fn is_potential_use(context: PlaceContext) -> bool {
match context {
// storage effects on a place do not activate it
PlaceContext::StorageLive | PlaceContext::StorageDead => false,
// validation effects do not activate a place
//
// FIXME: Should they? Is it just another read? Or can we
// guarantee it won't dereference the stored address? How
// "deep" does validation go?
PlaceContext::Validate => false,
// FIXME: This is here to not change behaviour from before
// AsmOutput existed, but it's not necessarily a pure overwrite.
// so it's possible this should activate the place.
PlaceContext::AsmOutput |
// pure overwrites of a place do not activate it. (note
// PlaceContext::Call is solely about dest place)
PlaceContext::Store | PlaceContext::Call => false,
// reads of a place *do* activate it
PlaceContext::Move |
PlaceContext::Copy |
PlaceContext::Drop |
PlaceContext::Inspect |
PlaceContext::Borrow { .. } |
PlaceContext::Projection(..) => true,
}
}
}
impl<'b, 'tcx: 'b> Visitor<'tcx> for ContainsUseOfPlace<'b, 'tcx> {
fn visit_place(&mut self,
place: &mir::Place<'tcx>,
context: PlaceContext<'tcx>,
location: Location) {
if Self::is_potential_use(context) && place == self.target {
self.use_found = true;
return;
// There is no need to keep checking the statement, we already found a use
}
self.super_place(place, context, location);
}
/*
fn visit_statement(&mut self,
block: BasicBlock,
statement: &mir::Statement<'tcx>,
location: Location) {
if let mir::StatementKind::StorageDead(loc) = *statement {
}
self.super_statement(block, statement, location);
}
*/
}
impl<'a, 'gcx, 'tcx> GatherBorrows<'a, 'gcx, 'tcx> {
/// Returns true if the borrow represented by `kind` is
/// allowed to be split into separate Reservation and
/// Activation phases.
fn allow_two_phase_borrow(&self, kind: mir::BorrowKind) -> bool {
self.tcx.sess.two_phase_borrows() &&
(kind.allows_two_phase_borrow() ||
self.tcx.sess.opts.debugging_opts.two_phase_beyond_autoref)
}
/// Returns true if the given location contains an NLL-activating use of the given place
fn location_contains_use(&self, location: Location, place: &Place) -> bool {
let mut use_checker = ContainsUseOfPlace::new(place);
let block = &self.mir.basic_blocks().get(location.block).unwrap_or_else(|| {
panic!("could not find block at location {:?}", location);
});
if location.statement_index != block.statements.len() {
// This is a statement
let stmt = block.statements.get(location.statement_index).unwrap_or_else(|| {
panic!("could not find statement at location {:?}");
});
use_checker.visit_statement(location.block, stmt, location);
} else {
// This is a terminator
match block.terminator {
Some(ref term) => {
use_checker.visit_terminator(location.block, term, location);
}
None => {
// There is no way for Place to be used by the terminator if there is no
// terminator
}
}
}
use_checker.use_found
}
/// Determines if the provided region is terminated after the provided location.
/// EndRegion statements terminate their enclosed region::Scope.
/// We also consult with the NLL region inference engine, should one be available
fn region_terminated_after(&self, region: Region<'tcx>, location: Location) -> bool {
let block_data = &self.mir[location.block];
if location.statement_index != block_data.statements.len() {
let stmt = &block_data.statements[location.statement_index];
if let mir::StatementKind::EndRegion(region_scope) = stmt.kind {
if &ReScope(region_scope) == region {
// We encountered an EndRegion statement that terminates the provided region
return true;
}
}
}
if let Some(ref regioncx) = self.nonlexical_regioncx {
if !regioncx.region_contains_point(region, location) {
// NLL says the region has ended already
return true;
}
}
false
}
/// Computes the activation location of a borrow.
/// The general idea is to start at the beginning of the region and perform a DFS
/// until we exit the region, either via an explicit EndRegion or because NLL tells
/// us so. If we find more than one valid activation point, we currently panic the
/// compiler since two-phase borrows are only currently supported for compiler-
/// generated code. More precisely, we only allow two-phase borrows for:
/// - Function calls (fn some_func(&mut self, ....))
/// - *Assign operators (a += b -> fn add_assign(&mut self, other: Self))
/// See
/// - https://github.com/rust-lang/rust/issues/48431
/// for detailed design notes.
/// See the TODO in the body of the function for notes on extending support to more
/// general two-phased borrows.
fn compute_activation_location(&self,
start_location: Location,
assigned_place: &mir::Place<'tcx>,
region: Region<'tcx>,
kind: mir::BorrowKind) -> Location {
debug!("Borrows::compute_activation_location({:?}, {:?}, {:?})",
start_location,
assigned_place,
region);
if !self.allow_two_phase_borrow(kind) {
debug!(" -> {:?}", start_location);
return start_location;
}
// Perform the DFS.
// `stack` is the stack of locations still under consideration
// `found_use` is an Option that becomes Some when we find a use
let mut stack = vec![start_location];
let mut found_use = None;
while let Some(curr_loc) = stack.pop() {
let block_data = &self.mir.basic_blocks()
.get(curr_loc.block)
.unwrap_or_else(|| {
panic!("could not find block at location {:?}", curr_loc);
});
if self.region_terminated_after(region, curr_loc) {
// No need to process this statement.
// It's either an EndRegion (and thus couldn't use assigned_place) or not
// contained in the NLL region and thus a use would be invalid
continue;
}
if self.location_contains_use(curr_loc, assigned_place) {
// TODO: Handle this case a little more gracefully. Perhaps collect
// all uses in a vector, and find the point in the CFG that dominates
// all of them?
// Right now this is sufficient though since there should only be exactly
// one borrow-activating use of the borrow.
assert!(found_use.is_none(), "Found secondary use of place");
found_use = Some(curr_loc);
}
// Push the points we should consider next.
if curr_loc.statement_index < block_data.statements.len() {
stack.push(curr_loc.successor_within_block());
} else {
stack.extend(block_data.terminator().successors().iter().map(
|&basic_block| {
Location {
statement_index: 0,
block: basic_block
}
}
))
}
}
let found_use = found_use.expect("Did not find use of two-phase place");
debug!(" -> {:?}", found_use);
found_use
}
}
}
/// Returns the span for the "end point" given region. This will
/// return `None` if NLL is enabled, since that concept has no
/// meaning there. Otherwise, return region span if it exists and
/// span for end of the function if it doesn't exist.
pub(crate) fn opt_region_end_span(&self, region: &Region) -> Option<Span> {
match self.nonlexical_regioncx {
Some(_) => None,
None => {
match self.region_span_map.get(region) {
Some(span) => Some(self.tcx.sess.codemap().end_point(*span)),
None => Some(self.tcx.sess.codemap().end_point(self.mir.span))
}
}
}
}
pub fn borrows(&self) -> &IndexVec<BorrowIndex, BorrowData<'tcx>> { &self.borrows }
@ -284,18 +524,24 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
}
/// Add all borrows to the kill set, if those borrows are out of scope at `location`.
///
/// `is_activations` tracks whether we are in the Reservations or
/// the ActiveBorrows flow analysis, and does not set the
/// activation kill bits in the former case. (Technically, we
/// could set those kill bits without such a guard, since they are
/// never gen'ed by Reservations in the first place. But it makes
/// the instrumentation and graph renderings nicer to leave
/// activations out when of the Reservations kill sets.)
/// That means either they went out of either a nonlexical scope, if we care about those
/// at the moment, or the location represents a lexical EndRegion
fn kill_loans_out_of_scope_at_location(&self,
sets: &mut BlockSets<ReserveOrActivateIndex>,
location: Location,
is_activations: bool) {
location: Location) {
/*
XXX: bob_twinkles reintroduce this
let block_data = &self.mir[location.block];
if location.statement_index != block_data.statements.len() {
let statement = &block_data.statements[location.statement_index];
if let mir::StatementKind::EndRegion(region_scope) = statement.kind {
for &borrow_index in &self.region_map[&ReScope(region_scope)] {
sets.kill(&ReserveOrActivateIndex::reserved(borrow_index));
sets.kill(&ReserveOrActivateIndex::active(borrow_index));
}
}
}
*/
if let Some(ref regioncx) = self.nonlexical_regioncx {
// NOTE: The state associated with a given `location`
// reflects the dataflow on entry to the statement. If it
@ -312,21 +558,46 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
let borrow_region = borrow_data.region.to_region_vid();
if !regioncx.region_contains_point(borrow_region, location) {
sets.kill(&ReserveOrActivateIndex::reserved(borrow_index));
if is_activations {
sets.kill(&ReserveOrActivateIndex::active(borrow_index));
}
sets.kill(&ReserveOrActivateIndex::active(borrow_index));
}
}
}
}
/// Models statement effect in Reservations and ActiveBorrows flow
/// analyses; `is activations` tells us if we are in the latter
/// case.
fn statement_effect_on_borrows(&self,
sets: &mut BlockSets<ReserveOrActivateIndex>,
location: Location,
is_activations: bool) {
fn kill_borrows_on_local(&self,
sets: &mut BlockSets<ReserveOrActivateIndex>,
local: &rustc::mir::Local)
{
if let Some(borrow_indexes) = self.local_map.get(local) {
sets.kill_all(borrow_indexes.iter()
.map(|b| ReserveOrActivateIndex::reserved(*b)));
sets.kill_all(borrow_indexes.iter()
.map(|b| ReserveOrActivateIndex::active(*b)));
}
}
}
impl<'a, 'gcx, 'tcx> BitDenotation for Borrows<'a, 'gcx, 'tcx> {
type Idx = ReserveOrActivateIndex;
fn name() -> &'static str { "borrows" }
fn bits_per_block(&self) -> usize {
self.borrows.len() * 2
}
fn start_block_effect(&self, _entry_set: &mut IdxSet<ReserveOrActivateIndex>) {
// no borrows of code region_scopes have been taken prior to
// function execution, so this method has no effect on
// `_sets`.
}
fn before_statement_effect(&self, sets: &mut BlockSets<ReserveOrActivateIndex>, location: Location) {
debug!("Borrows::before_statement_effect sets: {:?} location: {:?}", sets, location);
self.kill_loans_out_of_scope_at_location(sets, location);
}
fn statement_effect(&self, sets: &mut BlockSets<ReserveOrActivateIndex>, location: Location) {
debug!("Borrows::statement_effect sets: {:?} location: {:?}", sets, location);
let block = &self.mir.basic_blocks().get(location.block).unwrap_or_else(|| {
panic!("could not find block at location {:?}", location);
});
@ -334,20 +605,12 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
panic!("could not find statement at location {:?}");
});
// Do kills introduced by NLL before setting up any potential
// gens. (See NOTE in kill_loans_out_of_scope_at_location.)
self.kill_loans_out_of_scope_at_location(sets, location, is_activations);
if is_activations {
// INVARIANT: `sets.on_entry` accurately captures
// reservations on entry to statement (b/c
// accumulates_intrablock_state is overridden for
// ActiveBorrows).
//
// Now compute the activations generated by uses within
// the statement based on that reservation state.
let mut find = FindPlaceUses { sets, assigned_map: &self.assigned_map };
find.visit_statement(location.block, stmt, location);
// Handle activations
match self.activation_map.get(&location) {
Some(&activated) => {
sets.gen(&ReserveOrActivateIndex::active(activated))
}
None => {}
}
match stmt.kind {
@ -357,9 +620,7 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
assert!(self.nonlexical_regioncx.is_none());
for idx in borrow_indexes {
sets.kill(&ReserveOrActivateIndex::reserved(*idx));
if is_activations {
sets.kill(&ReserveOrActivateIndex::active(*idx));
}
sets.kill(&ReserveOrActivateIndex::active(*idx));
}
} else {
// (if there is no entry, then there are no borrows to be tracked)
@ -372,7 +633,7 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
if let Place::Local(ref local) = *lhs {
// FIXME: Handle the case in which we're assigning over
// a projection (`foo.bar`).
self.kill_borrows_on_local(sets, local, is_activations);
self.kill_borrows_on_local(sets, local);
}
// NOTE: if/when the Assign case is revised to inspect
@ -396,18 +657,17 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
}).contains(&index));
sets.gen(&ReserveOrActivateIndex::reserved(*index));
if is_activations {
// Issue #46746: Two-phase borrows handles
// stmts of form `Tmp = &mut Borrow` ...
match lhs {
Place::Local(..) | Place::Static(..) => {} // okay
Place::Projection(..) => {
// ... can assign into projections,
// e.g. `box (&mut _)`. Current
// conservative solution: force
// immediate activation here.
sets.gen(&ReserveOrActivateIndex::active(*index));
}
// Issue #46746: Two-phase borrows handles
// stmts of form `Tmp = &mut Borrow` ...
// XXX bob_twinkles experiment with removing this
match lhs {
Place::Local(..) | Place::Static(..) => {} // okay
Place::Projection(..) => {
// ... can assign into projections,
// e.g. `box (&mut _)`. Current
// conservative solution: force
// immediate activation here.
sets.gen(&ReserveOrActivateIndex::active(*index));
}
}
}
@ -416,7 +676,7 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
mir::StatementKind::StorageDead(local) => {
// Make sure there are no remaining borrows for locals that
// are gone out of scope.
self.kill_borrows_on_local(sets, &local, is_activations)
self.kill_borrows_on_local(sets, &local)
}
mir::StatementKind::InlineAsm { ref outputs, ref asm, .. } => {
@ -427,7 +687,7 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
if let Place::Local(ref local) = *output {
// FIXME: Handle the case in which we're assigning over
// a projection (`foo.bar`).
self.kill_borrows_on_local(sets, local, is_activations);
self.kill_borrows_on_local(sets, local);
}
}
}
@ -441,47 +701,26 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
}
}
fn kill_borrows_on_local(&self,
sets: &mut BlockSets<ReserveOrActivateIndex>,
local: &rustc::mir::Local,
is_activations: bool)
{
if let Some(borrow_indexes) = self.local_map.get(local) {
sets.kill_all(borrow_indexes.iter()
.map(|b| ReserveOrActivateIndex::reserved(*b)));
if is_activations {
sets.kill_all(borrow_indexes.iter()
.map(|b| ReserveOrActivateIndex::active(*b)));
}
}
fn before_terminator_effect(&self, sets: &mut BlockSets<ReserveOrActivateIndex>, location: Location) {
debug!("Borrows::before_terminator_effect sets: {:?} location: {:?}", sets, location);
self.kill_loans_out_of_scope_at_location(sets, location);
}
/// Models terminator effect in Reservations and ActiveBorrows
/// flow analyses; `is activations` tells us if we are in the
/// latter case.
fn terminator_effect_on_borrows(&self,
sets: &mut BlockSets<ReserveOrActivateIndex>,
location: Location,
is_activations: bool) {
fn terminator_effect(&self, sets: &mut BlockSets<ReserveOrActivateIndex>, location: Location) {
debug!("Borrows::terminator_effect sets: {:?} location: {:?}", sets, location);
let block = &self.mir.basic_blocks().get(location.block).unwrap_or_else(|| {
panic!("could not find block at location {:?}", location);
});
// Do kills introduced by NLL before setting up any potential
// gens. (See NOTE in kill_loans_out_of_scope_at_location.)
self.kill_loans_out_of_scope_at_location(sets, location, is_activations);
let term = block.terminator();
if is_activations {
// INVARIANT: `sets.on_entry` accurately captures
// reservations on entry to terminator (b/c
// accumulates_intrablock_state is overridden for
// ActiveBorrows).
//
// Now compute effect of the terminator on the activations
// themselves in the ActiveBorrows state.
let mut find = FindPlaceUses { sets, assigned_map: &self.assigned_map };
find.visit_terminator(location.block, term, location);
// Handle activations
match self.activation_map.get(&location) {
Some(&activated) => {
sets.gen(&ReserveOrActivateIndex::active(activated))
}
None => {}
}
match term.kind {
@ -504,9 +743,7 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
self.scope_tree.is_subscope_of(*scope, root_scope)
{
sets.kill(&ReserveOrActivateIndex::reserved(borrow_index));
if is_activations {
sets.kill(&ReserveOrActivateIndex::active(borrow_index));
}
sets.kill(&ReserveOrActivateIndex::active(borrow_index));
}
}
}
@ -525,161 +762,6 @@ impl<'a, 'gcx, 'tcx> Borrows<'a, 'gcx, 'tcx> {
mir::TerminatorKind::Unreachable => {}
}
}
}
impl<'a, 'gcx, 'tcx> ActiveBorrows<'a, 'gcx, 'tcx> {
pub(crate) fn borrows(&self) -> &IndexVec<BorrowIndex, BorrowData<'tcx>> {
self.0.borrows()
}
/// Returns the span for the "end point" given region. This will
/// return `None` if NLL is enabled, since that concept has no
/// meaning there. Otherwise, return region span if it exists and
/// span for end of the function if it doesn't exist.
pub(crate) fn opt_region_end_span(&self, region: &Region) -> Option<Span> {
match self.0.nonlexical_regioncx {
Some(_) => None,
None => {
match self.0.region_span_map.get(region) {
Some(span) => Some(self.0.tcx.sess.codemap().end_point(*span)),
None => Some(self.0.tcx.sess.codemap().end_point(self.0.mir.span))
}
}
}
}
}
/// `FindPlaceUses` is a MIR visitor that updates `self.sets` for all
/// of the borrows activated by a given statement or terminator.
///
/// ----
///
/// The `ActiveBorrows` flow analysis, when inspecting any given
/// statement or terminator, needs to "generate" (i.e. set to 1) all
/// of the bits for the borrows that are activated by that
/// statement/terminator.
///
/// This struct will seek out all places that are assignment-targets
/// for borrows (gathered in `self.assigned_map`; see also the
/// `assigned_map` in `struct Borrows`), and set the corresponding
/// gen-bits for activations of those borrows in `self.sets`
struct FindPlaceUses<'a, 'b: 'a, 'tcx: 'a> {
assigned_map: &'a FxHashMap<Place<'tcx>, FxHashSet<BorrowIndex>>,
sets: &'a mut BlockSets<'b, ReserveOrActivateIndex>,
}
impl<'a, 'b, 'tcx> FindPlaceUses<'a, 'b, 'tcx> {
fn has_been_reserved(&self, b: &BorrowIndex) -> bool {
self.sets.on_entry.contains(&ReserveOrActivateIndex::reserved(*b))
}
/// return whether `context` should be considered a "use" of a
/// place found in that context. "Uses" activate associated
/// borrows (at least when such uses occur while the borrow also
/// has a reservation at the time).
fn is_potential_use(context: PlaceContext) -> bool {
match context {
// storage effects on a place do not activate it
PlaceContext::StorageLive | PlaceContext::StorageDead => false,
// validation effects do not activate a place
//
// FIXME: Should they? Is it just another read? Or can we
// guarantee it won't dereference the stored address? How
// "deep" does validation go?
PlaceContext::Validate => false,
// FIXME: This is here to not change behaviour from before
// AsmOutput existed, but it's not necessarily a pure overwrite.
// so it's possible this should activate the place.
PlaceContext::AsmOutput |
// pure overwrites of a place do not activate it. (note
// PlaceContext::Call is solely about dest place)
PlaceContext::Store | PlaceContext::Call => false,
// reads of a place *do* activate it
PlaceContext::Move |
PlaceContext::Copy |
PlaceContext::Drop |
PlaceContext::Inspect |
PlaceContext::Borrow { .. } |
PlaceContext::Projection(..) => true,
}
}
}
impl<'a, 'b, 'tcx> Visitor<'tcx> for FindPlaceUses<'a, 'b, 'tcx> {
fn visit_place(&mut self,
place: &mir::Place<'tcx>,
context: PlaceContext<'tcx>,
location: Location) {
debug!("FindPlaceUses place: {:?} assigned from borrows: {:?} \
used in context: {:?} at location: {:?}",
place, self.assigned_map.get(place), context, location);
if Self::is_potential_use(context) {
if let Some(borrows) = self.assigned_map.get(place) {
for borrow_idx in borrows {
debug!("checking if index {:?} for {:?} is reserved ({}) \
and thus needs active gen-bit set in sets {:?}",
borrow_idx, place, self.has_been_reserved(&borrow_idx), self.sets);
if self.has_been_reserved(&borrow_idx) {
self.sets.gen(&ReserveOrActivateIndex::active(*borrow_idx));
} else {
// (This can certainly happen in valid code. I
// just want to know about it in the short
// term.)
debug!("encountered use of Place {:?} of borrow_idx {:?} \
at location {:?} outside of reservation",
place, borrow_idx, location);
}
}
}
}
self.super_place(place, context, location);
}
}
impl<'a, 'gcx, 'tcx> BitDenotation for Reservations<'a, 'gcx, 'tcx> {
type Idx = ReserveOrActivateIndex;
fn name() -> &'static str { "reservations" }
fn bits_per_block(&self) -> usize {
self.0.borrows.len() * 2
}
fn start_block_effect(&self, _entry_set: &mut IdxSet<ReserveOrActivateIndex>) {
// no borrows of code region_scopes have been taken prior to
// function execution, so this method has no effect on
// `_sets`.
}
fn before_statement_effect(&self,
sets: &mut BlockSets<ReserveOrActivateIndex>,
location: Location) {
debug!("Reservations::before_statement_effect sets: {:?} location: {:?}", sets, location);
self.0.kill_loans_out_of_scope_at_location(sets, location, false);
}
fn statement_effect(&self,
sets: &mut BlockSets<ReserveOrActivateIndex>,
location: Location) {
debug!("Reservations::statement_effect sets: {:?} location: {:?}", sets, location);
self.0.statement_effect_on_borrows(sets, location, false);
}
fn before_terminator_effect(&self,
sets: &mut BlockSets<ReserveOrActivateIndex>,
location: Location) {
debug!("Reservations::before_terminator_effect sets: {:?} location: {:?}", sets, location);
self.0.kill_loans_out_of_scope_at_location(sets, location, false);
}
fn terminator_effect(&self,
sets: &mut BlockSets<ReserveOrActivateIndex>,
location: Location) {
debug!("Reservations::terminator_effect sets: {:?} location: {:?}", sets, location);
self.0.terminator_effect_on_borrows(sets, location, false);
}
fn propagate_call_return(&self,
_in_out: &mut IdxSet<ReserveOrActivateIndex>,
@ -694,85 +776,17 @@ impl<'a, 'gcx, 'tcx> BitDenotation for Reservations<'a, 'gcx, 'tcx> {
}
}
impl<'a, 'gcx, 'tcx> BitDenotation for ActiveBorrows<'a, 'gcx, 'tcx> {
type Idx = ReserveOrActivateIndex;
fn name() -> &'static str { "active_borrows" }
/// Overriding this method; `ActiveBorrows` uses the intrablock
/// state in `on_entry` to track the current reservations (which
/// then affect the construction of the gen/kill sets for
/// activations).
fn accumulates_intrablock_state() -> bool { true }
fn bits_per_block(&self) -> usize {
self.0.borrows.len() * 2
}
fn start_block_effect(&self, _entry_sets: &mut IdxSet<ReserveOrActivateIndex>) {
// no borrows of code region_scopes have been taken prior to
// function execution, so this method has no effect on
// `_sets`.
}
fn before_statement_effect(&self,
sets: &mut BlockSets<ReserveOrActivateIndex>,
location: Location) {
debug!("ActiveBorrows::before_statement_effect sets: {:?} location: {:?}", sets, location);
self.0.kill_loans_out_of_scope_at_location(sets, location, true);
}
fn statement_effect(&self,
sets: &mut BlockSets<ReserveOrActivateIndex>,
location: Location) {
debug!("ActiveBorrows::statement_effect sets: {:?} location: {:?}", sets, location);
self.0.statement_effect_on_borrows(sets, location, true);
}
fn before_terminator_effect(&self,
sets: &mut BlockSets<ReserveOrActivateIndex>,
location: Location) {
debug!("ActiveBorrows::before_terminator_effect sets: {:?} location: {:?}", sets, location);
self.0.kill_loans_out_of_scope_at_location(sets, location, true);
}
fn terminator_effect(&self,
sets: &mut BlockSets<ReserveOrActivateIndex>,
location: Location) {
debug!("ActiveBorrows::terminator_effect sets: {:?} location: {:?}", sets, location);
self.0.terminator_effect_on_borrows(sets, location, true);
}
fn propagate_call_return(&self,
_in_out: &mut IdxSet<ReserveOrActivateIndex>,
_call_bb: mir::BasicBlock,
_dest_bb: mir::BasicBlock,
_dest_place: &mir::Place) {
// there are no effects on borrows from method call return...
//
// ... but If overwriting a place can affect flow state, then
// latter is not true; see NOTE on Assign case in
// statement_effect_on_borrows.
}
}
impl<'a, 'gcx, 'tcx> BitwiseOperator for Reservations<'a, 'gcx, 'tcx> {
impl<'a, 'gcx, 'tcx> BitwiseOperator for Borrows<'a, 'gcx, 'tcx> {
#[inline]
fn join(&self, pred1: usize, pred2: usize) -> usize {
pred1 | pred2 // union effects of preds when computing reservations
}
}
impl<'a, 'gcx, 'tcx> BitwiseOperator for ActiveBorrows<'a, 'gcx, 'tcx> {
#[inline]
fn join(&self, pred1: usize, pred2: usize) -> usize {
pred1 | pred2 // union effects of preds when computing activations
}
}
impl<'a, 'gcx, 'tcx> InitialFlow for Reservations<'a, 'gcx, 'tcx> {
impl<'a, 'gcx, 'tcx> InitialFlow for Borrows<'a, 'gcx, 'tcx> {
#[inline]
fn bottom_value() -> bool {
false // bottom = no Rvalue::Refs are reserved by default
false // bottom = nothing is reserved or activated yet
}
}

View file

@ -18,7 +18,7 @@ use rustc::ty::{self, TyCtxt};
use rustc::mir::{self, Mir, BasicBlock, BasicBlockData, Location, Statement, Terminator};
use rustc::session::Session;
use std::borrow::{Borrow, Cow};
use std::borrow::Borrow;
use std::fmt;
use std::io;
use std::mem;
@ -31,7 +31,7 @@ pub use self::impls::{DefinitelyInitializedPlaces, MovingOutStatements};
pub use self::impls::EverInitializedPlaces;
pub use self::impls::borrows::{Borrows, BorrowData};
pub use self::impls::HaveBeenBorrowedLocals;
pub(crate) use self::impls::borrows::{ActiveBorrows, Reservations, ReserveOrActivateIndex};
pub(crate) use self::impls::borrows::{ReserveOrActivateIndex};
pub use self::at_location::{FlowAtLocation, FlowsAtLocation};
pub(crate) use self::drop_flag_effects::*;
@ -584,9 +584,6 @@ impl<E:Idx> AllSets<E> {
pub fn on_entry_set_for(&self, block_idx: usize) -> &IdxSet<E> {
self.lookup_set_for(&self.on_entry_sets, block_idx)
}
pub(crate) fn entry_set_state(&self) -> &Bits<E> {
&self.on_entry_sets
}
}
/// Parameterization for the precise form of data flow that is used.
@ -739,27 +736,17 @@ impl<'a, 'tcx, D> DataflowAnalysis<'a, 'tcx, D> where D: BitDenotation
dead_unwinds: &'a IdxSet<mir::BasicBlock>,
denotation: D) -> Self where D: InitialFlow {
let bits_per_block = denotation.bits_per_block();
let usize_bits = mem::size_of::<usize>() * 8;
let words_per_block = (bits_per_block + usize_bits - 1) / usize_bits;
let num_overall = Self::num_bits_overall(mir, bits_per_block);
let zeroes = Bits::new(IdxSetBuf::new_empty(num_overall));
let on_entry = Bits::new(if D::bottom_value() {
IdxSetBuf::new_filled(num_overall)
} else {
IdxSetBuf::new_empty(num_overall)
});
Self::new_with_entry_sets(mir, dead_unwinds, Cow::Owned(on_entry), denotation)
}
pub(crate) fn new_with_entry_sets(mir: &'a Mir<'tcx>,
dead_unwinds: &'a IdxSet<mir::BasicBlock>,
on_entry: Cow<Bits<D::Idx>>,
denotation: D)
-> Self {
let bits_per_block = denotation.bits_per_block();
let usize_bits = mem::size_of::<usize>() * 8;
let words_per_block = (bits_per_block + usize_bits - 1) / usize_bits;
let num_overall = Self::num_bits_overall(mir, bits_per_block);
assert_eq!(num_overall, on_entry.bits.words().len() * usize_bits);
let zeroes = Bits::new(IdxSetBuf::new_empty(num_overall));
DataflowAnalysis {
mir,
dead_unwinds,
@ -769,13 +756,27 @@ impl<'a, 'tcx, D> DataflowAnalysis<'a, 'tcx, D> where D: BitDenotation
words_per_block,
gen_sets: zeroes.clone(),
kill_sets: zeroes,
on_entry_sets: on_entry.into_owned(),
on_entry_sets: on_entry,
},
operator: denotation,
}
}
}
pub fn new_from_sets(mir: &'a Mir<'tcx>,
dead_unwinds: &'a IdxSet<mir::BasicBlock>,
sets: AllSets<D::Idx>,
denotation: D) -> Self {
DataflowAnalysis {
mir,
dead_unwinds,
flow_state: DataflowState {
sets: sets,
operator: denotation,
}
}
}
fn num_bits_overall(mir: &Mir, bits_per_block: usize) -> usize {
let usize_bits = mem::size_of::<usize>() * 8;
let words_per_block = (bits_per_block + usize_bits - 1) / usize_bits;

View file

@ -10,10 +10,12 @@
// ignore-tidy-linelength
// revisions: lxl_beyond nll_beyond nll_target
// revisions: nll_target
// The following revisions are disabled due to missing support from two-phase beyond autorefs
//[lxl_beyond] compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z two-phase-beyond-autoref
//[nll_beyond] compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z two-phase-beyond-autoref -Z nll
//[nll_target] compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll
// This is an important corner case pointed out by Niko: one is

View file

@ -10,9 +10,13 @@
// ignore-tidy-linelength
// revisions: lxl_beyond nll_beyond nll_target
// revisions: nll_target
// The following revisions are disabled due to missing support for two_phase_beyond_autoref
//[lxl_beyond] compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z two_phase_beyond_autoref
//[nll_beyond] compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z two_phase_beyond_autoref -Z nll
//[nll_target] compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll
// This is the second counter-example from Niko's blog post

View file

@ -8,10 +8,12 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// revisions: lxl nll g2p
// revisions: lxl nll
//[lxl]compile-flags: -Z borrowck=mir -Z two-phase-borrows
//[nll]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll
//[g2p]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll -Z two-phase-beyond-autoref
// the above revision is disabled until two-phase-beyond-autoref support is better
// This is a test checking that when we limit two-phase borrows to
// method receivers, we do not let other kinds of auto-ref to leak
@ -70,10 +72,8 @@ fn overloaded_call_traits() {
fn twice_ten_sm<F: FnMut(i32) -> i32>(f: &mut F) {
f(f(10));
//[lxl]~^ ERROR cannot borrow `*f` as mutable more than once at a time
//[lxl]~| ERROR cannot borrow `*f` as mutable more than once at a time
//[nll]~^^^ ERROR cannot borrow `*f` as mutable more than once at a time
//[nll]~| ERROR cannot borrow `*f` as mutable more than once at a time
//[g2p]~^^^^^ ERROR cannot borrow `*f` as mutable more than once at a time
//[nll]~^^ ERROR cannot borrow `*f` as mutable more than once at a time
//[g2p]~^^^ ERROR cannot borrow `*f` as mutable more than once at a time
}
fn twice_ten_si<F: Fn(i32) -> i32>(f: &mut F) {
f(f(10));
@ -88,10 +88,8 @@ fn overloaded_call_traits() {
fn twice_ten_om(f: &mut FnMut(i32) -> i32) {
f(f(10));
//[lxl]~^ ERROR cannot borrow `*f` as mutable more than once at a time
//[lxl]~| ERROR cannot borrow `*f` as mutable more than once at a time
//[nll]~^^^ ERROR cannot borrow `*f` as mutable more than once at a time
//[nll]~| ERROR cannot borrow `*f` as mutable more than once at a time
//[g2p]~^^^^^ ERROR cannot borrow `*f` as mutable more than once at a time
//[nll]~^^ ERROR cannot borrow `*f` as mutable more than once at a time
//[g2p]~^^^ ERROR cannot borrow `*f` as mutable more than once at a time
}
fn twice_ten_oi(f: &mut Fn(i32) -> i32) {
f(f(10));

View file

@ -12,8 +12,12 @@
// revisions: lxl_beyond nll_beyond nll_target
// The following revisions are disabled due to missing support from two-phase beyond autorefs
//[lxl_beyond]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z two-phase-beyond-autoref
//[lxl_beyond] should-fail
//[nll_beyond]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z two-phase-beyond-autoref -Z nll
//[nll_beyond] should-fail
//[nll_target]compile-flags: -Z borrowck=mir -Z two-phase-borrows -Z nll
// This is a corner case that the current implementation is (probably)
@ -31,10 +35,6 @@
// "nll_beyond" means the generalization of two-phase borrows to all
// `&mut`-borrows (doing so makes it easier to write code for specific
// corner cases).
//
// FIXME: in "nll_target", we currently see the same error reported
// twice. This is injected by `-Z two-phase-borrows`; not sure why as
// of yet.
fn main() {
let mut vec = vec![0, 1];
@ -49,7 +49,6 @@ fn main() {
//[lxl_beyond]~^ ERROR cannot borrow `vec` as mutable because it is also borrowed as immutable
//[nll_beyond]~^^ ERROR cannot borrow `vec` as mutable because it is also borrowed as immutable
//[nll_target]~^^^ ERROR cannot borrow `vec` as mutable because it is also borrowed as immutable
//[nll_target]~| ERROR cannot borrow `vec` as mutable because it is also borrowed as immutable
shared[0];
}