Do not mark unitinitialized locals as requiring storage

This commit is contained in:
Matthew Jasper 2019-09-29 21:34:22 +01:00
parent 1dfc3e7962
commit 73c09870b5
5 changed files with 140 additions and 17 deletions

View file

@ -109,15 +109,13 @@ impl<'mir, 'tcx> BitDenotation<'tcx> for RequiresStorage<'mir, 'tcx> {
assert_eq!(1, self.body.arg_count);
}
fn statement_effect(&self,
sets: &mut GenKillSet<Local>,
loc: Location) {
self.check_for_move(sets, loc);
fn before_statement_effect(&self, sets: &mut GenKillSet<Self::Idx>, loc: Location) {
// If we borrow or assign to a place then it needs storage for that
// statement.
self.check_for_borrow(sets, loc);
let stmt = &self.body[loc.block].statements[loc.statement_index];
match stmt.kind {
StatementKind::StorageLive(l) => sets.gen(l),
StatementKind::StorageDead(l) => sets.kill(l),
StatementKind::Assign(box(ref place, _))
| StatementKind::SetDiscriminant { box ref place, .. } => {
@ -136,11 +134,35 @@ impl<'mir, 'tcx> BitDenotation<'tcx> for RequiresStorage<'mir, 'tcx> {
}
}
fn terminator_effect(&self,
sets: &mut GenKillSet<Local>,
loc: Location) {
fn statement_effect(&self, sets: &mut GenKillSet<Local>, loc: Location) {
// If we move from a place then only stops needing storage *after*
// that statement.
self.check_for_move(sets, loc);
}
fn before_terminator_effect(&self, sets: &mut GenKillSet<Local>, loc: Location) {
self.check_for_borrow(sets, loc);
if let TerminatorKind::Call {
destination: Some((Place { base: PlaceBase::Local(local), .. }, _)),
..
} = self.body[loc.block].terminator().kind {
sets.gen(local);
}
}
fn terminator_effect(&self, sets: &mut GenKillSet<Local>, loc: Location) {
// For call terminators the destination requires storage for the call
// and after the call returns successfully, but not after a panic.
// Since `propagate_call_unwind` doesn't exist, we have to kill the
// destination here, and then gen it again in `propagate_call_return`.
if let TerminatorKind::Call {
destination: Some((Place { base: PlaceBase::Local(local), projection: box [] }, _)),
..
} = self.body[loc.block].terminator().kind {
sets.kill(local);
}
self.check_for_move(sets, loc);
}
fn propagate_call_return(

View file

@ -508,10 +508,7 @@ fn locals_live_across_suspend_points(
storage_liveness_map.insert(block, storage_liveness.clone());
requires_storage_cursor.seek(loc);
let mut storage_required = requires_storage_cursor.get().clone();
// Mark locals without storage statements as always requiring storage
storage_required.union(&ignored.0);
let storage_required = requires_storage_cursor.get().clone();
// Locals live are live at this point only if they are used across
// suspension points (the `liveness` variable)

View file

@ -22,7 +22,8 @@ struct BigFut([u8; BIG_FUT_SIZE]);
impl BigFut {
fn new() -> Self {
BigFut([0; BIG_FUT_SIZE])
} }
}
}
impl Drop for BigFut {
fn drop(&mut self) {}

View file

@ -0,0 +1,103 @@
// Test that we don't store uninitialized locals in futures from `async fn`.
//
// The exact sizes can change by a few bytes (we'd like to know when they do).
// What we don't want to see is the wrong multiple of 1024 (the size of `Big`)
// being reflected in the size.
// ignore-wasm32-bare (sizes don't match)
// run-pass
// edition:2018
#![allow(unused_variables, unused_assignments)]
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
const BIG_FUT_SIZE: usize = 1024;
struct Big([u8; BIG_FUT_SIZE]);
impl Big {
fn new() -> Self {
Big([0; BIG_FUT_SIZE])
}
}
impl Drop for Big {
fn drop(&mut self) {}
}
#[allow(dead_code)]
struct Joiner {
a: Option<Big>,
b: Option<Big>,
c: Option<Big>,
}
impl Future for Joiner {
type Output = ();
fn poll(self: Pin<&mut Self>, _ctx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}
fn noop() {}
async fn fut() {}
async fn single() {
let x;
fut().await;
x = Big::new();
}
async fn single_with_noop() {
let x;
fut().await;
noop();
x = Big::new();
noop();
}
async fn joined() {
let joiner;
let a = Big::new();
let b = Big::new();
let c = Big::new();
fut().await;
noop();
joiner = Joiner { a: Some(a), b: Some(b), c: Some(c) };
noop();
}
async fn joined_with_noop() {
let joiner;
let a = Big::new();
let b = Big::new();
let c = Big::new();
fut().await;
noop();
joiner = Joiner { a: Some(a), b: Some(b), c: Some(c) };
noop();
}
async fn join_retval() -> Joiner {
let a = Big::new();
let b = Big::new();
let c = Big::new();
fut().await;
noop();
Joiner { a: Some(a), b: Some(b), c: Some(c) }
}
fn main() {
assert_eq!(8, std::mem::size_of_val(&single()));
assert_eq!(12, std::mem::size_of_val(&single_with_noop()));
assert_eq!(3084, std::mem::size_of_val(&joined()));
assert_eq!(3084, std::mem::size_of_val(&joined_with_noop()));
assert_eq!(3084, std::mem::size_of_val(&join_retval()));
}

View file

@ -89,10 +89,10 @@ fn main() {
assert_eq!(8, std::mem::size_of_val(&await1_level1()));
assert_eq!(12, std::mem::size_of_val(&await2_level1()));
assert_eq!(12, std::mem::size_of_val(&await3_level1()));
assert_eq!(20, std::mem::size_of_val(&await3_level2()));
assert_eq!(28, std::mem::size_of_val(&await3_level3()));
assert_eq!(36, std::mem::size_of_val(&await3_level4()));
assert_eq!(44, std::mem::size_of_val(&await3_level5()));
assert_eq!(24, std::mem::size_of_val(&await3_level2()));
assert_eq!(36, std::mem::size_of_val(&await3_level3()));
assert_eq!(48, std::mem::size_of_val(&await3_level4()));
assert_eq!(60, std::mem::size_of_val(&await3_level5()));
assert_eq!(1, wait(base()));
assert_eq!(1, wait(await1_level1()));