rustc: optimize out uninhabited types and variants.

This commit is contained in:
Eduard-Mihai Burtescu 2017-09-26 21:34:10 +03:00
parent f62e43da28
commit ced5e04e8b
11 changed files with 197 additions and 92 deletions

View File

@ -755,6 +755,7 @@ impl FieldPlacement {
/// in terms of categories of C types there are ABI rules for.
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum Abi {
Uninhabited,
Scalar(Scalar),
Vector,
Aggregate {
@ -768,7 +769,7 @@ impl Abi {
/// Returns true if the layout corresponds to an unsized type.
pub fn is_unsized(&self) -> bool {
match *self {
Abi::Scalar(_) | Abi::Vector => false,
Abi::Uninhabited | Abi::Scalar(_) | Abi::Vector => false,
Abi::Aggregate { sized, .. } => !sized
}
}
@ -776,7 +777,7 @@ impl Abi {
/// Returns true if the fields of the layout are packed.
pub fn is_packed(&self) -> bool {
match *self {
Abi::Scalar(_) | Abi::Vector => false,
Abi::Uninhabited | Abi::Scalar(_) | Abi::Vector => false,
Abi::Aggregate { packed, .. } => packed
}
}
@ -807,6 +808,7 @@ pub enum Variants {
/// `Some` is the identity function (with a non-null reference).
NicheFilling {
dataful_variant: usize,
niche_variant: usize,
niche: Scalar,
niche_value: u128,
variants: Vec<CachedLayout>,
@ -855,6 +857,18 @@ impl CachedLayout {
primitive_align: align
}
}
fn uninhabited(field_count: usize) -> Self {
let align = Align::from_bytes(1, 1).unwrap();
CachedLayout {
variants: Variants::Single { index: 0 },
fields: FieldPlacement::Union(field_count),
abi: Abi::Uninhabited,
align,
primitive_align: align,
size: Size::from_bytes(0)
}
}
}
fn layout_raw<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
@ -915,13 +929,14 @@ impl<'a, 'tcx> CachedLayout {
bug!("struct cannot be packed and aligned");
}
let mut align = if packed {
let base_align = if packed {
dl.i8_align
} else {
dl.aggregate_align
};
let mut primitive_align = align;
let mut align = base_align;
let mut primitive_align = base_align;
let mut sized = true;
// Anything with repr(C) or repr(packed) doesn't optimize.
@ -978,13 +993,17 @@ impl<'a, 'tcx> CachedLayout {
}
}
for i in inverse_memory_index.iter() {
let field = fields[*i as usize];
for &i in &inverse_memory_index {
let field = fields[i as usize];
if !sized {
bug!("univariant: field #{} of `{}` comes after unsized field",
offsets.len(), ty);
}
if field.abi == Abi::Uninhabited {
return Ok(CachedLayout::uninhabited(fields.len()));
}
if field.is_unsized() {
sized = false;
}
@ -997,7 +1016,7 @@ impl<'a, 'tcx> CachedLayout {
}
debug!("univariant offset: {:?} field: {:#?}", offset, field);
offsets[*i as usize] = offset;
offsets[i as usize] = offset;
offset = offset.checked_add(field.size, dl)
.ok_or(LayoutError::SizeOverflow(ty))?;
@ -1124,7 +1143,7 @@ impl<'a, 'tcx> CachedLayout {
// The never type.
ty::TyNever => {
univariant(&[], &ReprOptions::default(), StructKind::AlwaysSized)?
tcx.intern_layout(CachedLayout::uninhabited(0))
}
// Potentially-fat pointers.
@ -1278,11 +1297,15 @@ impl<'a, 'tcx> CachedLayout {
}).collect::<Result<Vec<_>, _>>()
}).collect::<Result<Vec<_>, _>>()?;
if variants.is_empty() {
// Uninhabitable; represent as unit
// (Typechecking will reject discriminant-sizing attrs.)
return univariant(&[], &def.repr, StructKind::AlwaysSized);
let (inh_first, inh_second, inh_third) = {
let mut inh_variants = (0..variants.len()).filter(|&v| {
variants[v].iter().all(|f| f.abi != Abi::Uninhabited)
});
(inh_variants.next(), inh_variants.next(), inh_variants.next())
};
if inh_first.is_none() {
// Uninhabited because it has no variants, or only uninhabited ones.
return Ok(tcx.intern_layout(CachedLayout::uninhabited(0)));
}
if def.is_union() {
@ -1329,49 +1352,58 @@ impl<'a, 'tcx> CachedLayout {
}));
}
if !def.is_enum() || (variants.len() == 1 &&
!def.repr.inhibit_enum_layout_opt() &&
!variants[0].is_empty()) {
// Struct, or union, or univariant enum equivalent to a struct.
let is_struct = !def.is_enum() ||
// Only one variant is inhabited.
(inh_second.is_none() &&
// Representation optimizations are allowed.
!def.repr.inhibit_enum_layout_opt() &&
// Inhabited variant either has data ...
(!variants[inh_first.unwrap()].is_empty() ||
// ... or there other, uninhabited, variants.
variants.len() > 1));
if is_struct {
// Struct, or univariant enum equivalent to a struct.
// (Typechecking will reject discriminant-sizing attrs.)
let kind = if def.is_enum() || variants[0].len() == 0 {
let v = inh_first.unwrap();
let kind = if def.is_enum() || variants[v].len() == 0 {
StructKind::AlwaysSized
} else {
let param_env = tcx.param_env(def.did);
let last_field = def.variants[0].fields.last().unwrap();
let last_field = def.variants[v].fields.last().unwrap();
let always_sized = tcx.type_of(last_field.did)
.is_sized(tcx, param_env, DUMMY_SP);
if !always_sized { StructKind::MaybeUnsized }
else { StructKind::AlwaysSized }
};
return univariant(&variants[0], &def.repr, kind);
let mut st = univariant_uninterned(&variants[v], &def.repr, kind)?;
st.variants = Variants::Single { index: v };
return Ok(tcx.intern_layout(st));
}
let no_explicit_discriminants = def.variants.iter().enumerate()
.all(|(i, v)| v.discr == ty::VariantDiscr::Relative(i));
if variants.len() == 2 &&
if inh_second.is_some() && inh_third.is_none() &&
!def.repr.inhibit_enum_layout_opt() &&
no_explicit_discriminants {
// Nullable pointer optimization
for i in 0..2 {
if !variants[1 - i].iter().all(|f| f.is_zst()) {
let (a, b) = (inh_first.unwrap(), inh_second.unwrap());
for &(i, other) in &[(a, b), (b, a)] {
if !variants[other].iter().all(|f| f.is_zst()) {
continue;
}
for (field_index, field) in variants[i].iter().enumerate() {
if let Some((offset, niche, niche_value)) = field.find_niche(cx)? {
let mut st = vec![
univariant_uninterned(&variants[0],
&def.repr, StructKind::AlwaysSized)?,
univariant_uninterned(&variants[1],
&def.repr, StructKind::AlwaysSized)?
];
for (i, v) in st.iter_mut().enumerate() {
v.variants = Variants::Single { index: i };
}
let st = variants.iter().enumerate().map(|(j, v)| {
let mut st = univariant_uninterned(v,
&def.repr, StructKind::AlwaysSized)?;
st.variants = Variants::Single { index: j };
Ok(st)
}).collect::<Result<Vec<_>, _>>()?;
let offset = st[i].fields.offset(field_index) + offset;
let CachedLayout {
size,
@ -1400,6 +1432,7 @@ impl<'a, 'tcx> CachedLayout {
return Ok(tcx.intern_layout(CachedLayout {
variants: Variants::NicheFilling {
dataful_variant: i,
niche_variant: other,
niche,
niche_value,
variants: st,
@ -1419,11 +1452,15 @@ impl<'a, 'tcx> CachedLayout {
}
let (mut min, mut max) = (i128::max_value(), i128::min_value());
for discr in def.discriminants(tcx) {
for (i, discr) in def.discriminants(tcx).enumerate() {
if variants[i].iter().any(|f| f.abi == Abi::Uninhabited) {
continue;
}
let x = discr.to_u128_unchecked() as i128;
if x < min { min = x; }
if x > max { max = x; }
}
assert!(min <= max, "discriminant range is {}...{}", min, max);
let (min_ity, signed) = Integer::repr_discr(tcx, ty, &def.repr, min, max);
let mut align = dl.aggregate_align;
@ -1498,6 +1535,9 @@ impl<'a, 'tcx> CachedLayout {
let old_ity_size = min_ity.size();
let new_ity_size = ity.size();
for variant in &mut variants {
if variant.abi == Abi::Uninhabited {
continue;
}
match variant.fields {
FieldPlacement::Arbitrary { ref mut offsets, .. } => {
for i in offsets {
@ -1663,16 +1703,11 @@ impl<'a, 'tcx> CachedLayout {
};
match layout.variants {
Variants::Single { .. } => {
let variant_names = || {
adt_def.variants.iter().map(|v|format!("{}", v.name)).collect::<Vec<_>>()
};
debug!("print-type-size `{:#?}` variants: {:?}",
layout, variant_names());
assert!(adt_def.variants.len() <= 1,
"univariant with variants {:?}", variant_names());
if adt_def.variants.len() == 1 {
let variant_def = &adt_def.variants[0];
Variants::Single { index } => {
debug!("print-type-size `{:#?}` variant {}",
layout, adt_def.variants[index].name);
if !adt_def.variants.is_empty() {
let variant_def = &adt_def.variants[index];
let fields: Vec<_> =
variant_def.fields.iter().map(|f| f.name).collect();
record(adt_kind.into(),
@ -1697,7 +1732,7 @@ impl<'a, 'tcx> CachedLayout {
variant_def.fields.iter().map(|f| f.name).collect();
build_variant_info(Some(variant_def.name),
&fields,
layout.for_variant(i))
layout.for_variant(cx, i))
})
.collect();
record(adt_kind.into(), match layout.variants {
@ -1989,15 +2024,35 @@ impl<'a, 'tcx> LayoutOf<Ty<'tcx>> for (ty::maps::TyCtxtAt<'a, 'tcx, 'tcx>,
}
impl<'a, 'tcx> TyLayout<'tcx> {
pub fn for_variant(&self, variant_index: usize) -> Self {
pub fn for_variant<C>(&self, cx: C, variant_index: usize) -> Self
where C: LayoutOf<Ty<'tcx>> + HasTyCtxt<'tcx>,
C::TyLayout: MaybeResult<TyLayout<'tcx>>
{
let cached = match self.variants {
Variants::Single { .. } => self.cached,
Variants::Single { index } if index == variant_index => self.cached,
Variants::Single { index } => {
// Deny calling for_variant more than once for non-Single enums.
cx.layout_of(self.ty).map_same(|layout| {
assert_eq!(layout.variants, Variants::Single { index });
layout
});
let fields = match self.ty.sty {
ty::TyAdt(def, _) => def.variants[variant_index].fields.len(),
_ => bug!()
};
let mut cached = CachedLayout::uninhabited(fields);
cached.variants = Variants::Single { index: variant_index };
cx.tcx().intern_layout(cached)
}
Variants::NicheFilling { ref variants, .. } |
Variants::Tagged { ref variants, .. } => {
&variants[variant_index]
}
};
assert_eq!(cached.variants, Variants::Single { index: variant_index });
TyLayout {
@ -2138,6 +2193,7 @@ impl<'a, 'tcx> TyLayout<'tcx> {
/// Returns true if the type is a ZST and not unsized.
pub fn is_zst(&self) -> bool {
match self.abi {
Abi::Uninhabited => true,
Abi::Scalar(_) => false,
Abi::Vector => self.size.bytes() == 0,
Abi::Aggregate { sized, .. } => sized && self.size.bytes() == 0
@ -2241,11 +2297,13 @@ impl<'gcx> HashStable<StableHashingContext<'gcx>> for Variants {
}
NicheFilling {
dataful_variant,
niche_variant,
ref niche,
niche_value,
ref variants,
} => {
dataful_variant.hash_stable(hcx, hasher);
niche_variant.hash_stable(hcx, hasher);
niche.hash_stable(hcx, hasher);
niche_value.hash_stable(hcx, hasher);
variants.hash_stable(hcx, hasher);
@ -2285,6 +2343,7 @@ impl<'gcx> HashStable<StableHashingContext<'gcx>> for Abi {
mem::discriminant(self).hash_stable(hcx, hasher);
match *self {
Uninhabited => {}
Scalar(ref value) => {
value.hash_stable(hcx, hasher);
}

View File

@ -278,6 +278,7 @@ pub trait LayoutExt<'tcx> {
impl<'tcx> LayoutExt<'tcx> for TyLayout<'tcx> {
fn is_aggregate(&self) -> bool {
match self.abi {
layout::Abi::Uninhabited |
layout::Abi::Scalar(_) |
layout::Abi::Vector => false,
layout::Abi::Aggregate { .. } => true
@ -286,6 +287,8 @@ impl<'tcx> LayoutExt<'tcx> for TyLayout<'tcx> {
fn homogeneous_aggregate<'a>(&self, ccx: &CrateContext<'a, 'tcx>) -> Option<Reg> {
match self.abi {
layout::Abi::Uninhabited => None,
// The primitive for this algorithm.
layout::Abi::Scalar(ref scalar) => {
let kind = match scalar.value {

View File

@ -65,6 +65,8 @@ fn classify_arg<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>, arg: &ArgType<'tcx>)
}
match layout.abi {
layout::Abi::Uninhabited => {}
layout::Abi::Scalar(ref scalar) => {
let reg = match scalar.value {
layout::Int(..) |

View File

@ -17,6 +17,7 @@ use rustc::ty::layout;
pub fn compute_abi_info(fty: &mut FnType) {
let fixup = |a: &mut ArgType| {
match a.layout.abi {
layout::Abi::Uninhabited => {}
layout::Abi::Aggregate { .. } => {
match a.layout.size.bits() {
8 => a.cast_to(Reg::i8()),

View File

@ -1130,43 +1130,38 @@ impl<'tcx> EnumMemberDescriptionFactory<'tcx> {
-> Vec<MemberDescription> {
let adt = &self.enum_type.ty_adt_def().unwrap();
match self.layout.variants {
layout::Variants::Single { .. } => {
assert!(adt.variants.len() <= 1);
layout::Variants::Single { .. } if adt.variants.is_empty() => vec![],
layout::Variants::Single { index } => {
let (variant_type_metadata, member_description_factory) =
describe_enum_variant(cx,
self.layout,
&adt.variants[index],
NoDiscriminant,
self.containing_scope,
self.span);
if adt.variants.is_empty() {
vec![]
} else {
let (variant_type_metadata, member_description_factory) =
describe_enum_variant(cx,
self.layout,
&adt.variants[0],
NoDiscriminant,
self.containing_scope,
self.span);
let member_descriptions =
member_description_factory.create_member_descriptions(cx);
let member_descriptions =
member_description_factory.create_member_descriptions(cx);
set_members_of_composite_type(cx,
variant_type_metadata,
&member_descriptions[..]);
vec![
MemberDescription {
name: "".to_string(),
type_metadata: variant_type_metadata,
offset: Size::from_bytes(0),
size: self.layout.size,
align: self.layout.align,
flags: DIFlags::FlagZero
}
]
}
set_members_of_composite_type(cx,
variant_type_metadata,
&member_descriptions[..]);
vec![
MemberDescription {
name: "".to_string(),
type_metadata: variant_type_metadata,
offset: Size::from_bytes(0),
size: self.layout.size,
align: self.layout.align,
flags: DIFlags::FlagZero
}
]
}
layout::Variants::Tagged { ref variants, .. } => {
let discriminant_info = RegularDiscriminant(self.discriminant_type_metadata
.expect(""));
(0..variants.len()).map(|i| {
let variant = self.layout.for_variant(i);
let variant = self.layout.for_variant(cx, i);
let (variant_type_metadata, member_desc_factory) =
describe_enum_variant(cx,
variant,
@ -1191,8 +1186,8 @@ impl<'tcx> EnumMemberDescriptionFactory<'tcx> {
}
}).collect()
}
layout::Variants::NicheFilling { dataful_variant, .. } => {
let variant = self.layout.for_variant(dataful_variant);
layout::Variants::NicheFilling { dataful_variant, niche_variant, .. } => {
let variant = self.layout.for_variant(cx, dataful_variant);
// Create a description of the non-null variant
let (variant_type_metadata, member_description_factory) =
describe_enum_variant(cx,
@ -1236,7 +1231,7 @@ impl<'tcx> EnumMemberDescriptionFactory<'tcx> {
self.layout,
self.layout.fields.offset(0),
self.layout.field(cx, 0).size);
name.push_str(&adt.variants[1 - dataful_variant].name.as_str());
name.push_str(&adt.variants[niche_variant].name.as_str());
// Create the (singleton) list of descriptions of union members.
vec![

View File

@ -710,7 +710,11 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> {
Immediate(llval) => {
for i in 0..tuple.layout.fields.count() {
let field = tuple.layout.field(bcx.ccx, i);
let elem = bcx.extract_value(llval, tuple.layout.llvm_field_index(i));
let elem = if field.is_zst() {
C_undef(field.llvm_type(bcx.ccx))
} else {
bcx.extract_value(llval, tuple.layout.llvm_field_index(i))
};
// If the tuple is immediate, the elements are as well
let op = OperandRef {
val: Immediate(base::to_immediate(bcx, elem, field)),

View File

@ -1099,6 +1099,11 @@ fn trans_const_adt<'a, 'tcx>(
mir::AggregateKind::Adt(_, index, _, _) => index,
_ => 0,
};
if let layout::Abi::Uninhabited = l.abi {
return Const::new(C_undef(l.llvm_type(ccx)), t);
}
match l.variants {
layout::Variants::Single { index } => {
assert_eq!(variant_index, index);
@ -1114,7 +1119,6 @@ fn trans_const_adt<'a, 'tcx>(
Const::new(C_struct(ccx, &contents, l.is_packed()), t)
} else {
assert_eq!(variant_index, 0);
build_const_struct(ccx, l, vals, None)
}
}
@ -1132,12 +1136,12 @@ fn trans_const_adt<'a, 'tcx>(
Const::new(discr, t)
} else {
let discr = Const::new(discr, discr_field.ty);
build_const_struct(ccx, l.for_variant(variant_index), vals, Some(discr))
build_const_struct(ccx, l.for_variant(ccx, variant_index), vals, Some(discr))
}
}
layout::Variants::NicheFilling { dataful_variant, niche_value, .. } => {
if variant_index == dataful_variant {
build_const_struct(ccx, l.for_variant(dataful_variant), vals, None)
build_const_struct(ccx, l.for_variant(ccx, dataful_variant), vals, None)
} else {
let niche = l.field(ccx, 0);
let niche_llty = niche.llvm_type(ccx);

View File

@ -115,7 +115,7 @@ impl<'a, 'tcx> LvalueRef<'tcx> {
assert_eq!(count, 0);
self.llextra
} else {
common::C_usize(ccx, count)
C_usize(ccx, count)
}
} else {
bug!("unexpected layout `{:#?}` in LvalueRef::len", self.layout)
@ -304,7 +304,12 @@ impl<'a, 'tcx> LvalueRef<'tcx> {
};
bcx.intcast(lldiscr, cast_to, signed)
}
layout::Variants::NicheFilling { dataful_variant, niche_value, .. } => {
layout::Variants::NicheFilling {
dataful_variant,
niche_variant,
niche_value,
..
} => {
let niche_llty = discr.layout.llvm_type(bcx.ccx);
// FIXME(eddyb) Check the actual primitive type here.
let niche_llval = if niche_value == 0 {
@ -313,8 +318,9 @@ impl<'a, 'tcx> LvalueRef<'tcx> {
} else {
C_uint_big(niche_llty, niche_value)
};
let cmp = if dataful_variant == 0 { llvm::IntEQ } else { llvm::IntNE };
bcx.intcast(bcx.icmp(cmp, lldiscr, niche_llval), cast_to, false)
bcx.select(bcx.icmp(llvm::IntEQ, lldiscr, niche_llval),
C_uint(cast_to, niche_variant as u64),
C_uint(cast_to, dataful_variant as u64))
}
}
}
@ -324,7 +330,12 @@ impl<'a, 'tcx> LvalueRef<'tcx> {
pub fn trans_set_discr(&self, bcx: &Builder<'a, 'tcx>, variant_index: usize) {
match self.layout.variants {
layout::Variants::Single { index } => {
assert_eq!(variant_index, index);
if index != variant_index {
// If the layout of an enum is `Single`, all
// other variants are necessarily uninhabited.
assert_eq!(self.layout.for_variant(bcx.ccx, variant_index).abi,
layout::Abi::Uninhabited);
}
}
layout::Variants::Tagged { .. } => {
let ptr = self.project_field(bcx, 0);
@ -366,7 +377,7 @@ impl<'a, 'tcx> LvalueRef<'tcx> {
pub fn project_index(&self, bcx: &Builder<'a, 'tcx>, llindex: ValueRef)
-> LvalueRef<'tcx> {
LvalueRef {
llval: bcx.inbounds_gep(self.llval, &[common::C_usize(bcx.ccx, 0), llindex]),
llval: bcx.inbounds_gep(self.llval, &[C_usize(bcx.ccx, 0), llindex]),
llextra: ptr::null_mut(),
layout: self.layout.field(bcx.ccx, 0),
alignment: self.alignment
@ -376,7 +387,7 @@ impl<'a, 'tcx> LvalueRef<'tcx> {
pub fn project_downcast(&self, bcx: &Builder<'a, 'tcx>, variant_index: usize)
-> LvalueRef<'tcx> {
let mut downcast = *self;
downcast.layout = self.layout.for_variant(variant_index);
downcast.layout = self.layout.for_variant(bcx.ccx, variant_index);
// Cast to the appropriate variant struct type.
let variant_ty = downcast.layout.llvm_type(bcx.ccx);

View File

@ -27,6 +27,7 @@ fn uncached_llvm_type<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>,
return Type::vector(&layout.field(ccx, 0).llvm_type(ccx),
layout.fields.count() as u64);
}
layout::Abi::Uninhabited |
layout::Abi::Aggregate { .. } => {}
}
@ -158,7 +159,9 @@ pub trait LayoutLlvmExt<'tcx> {
impl<'tcx> LayoutLlvmExt<'tcx> for TyLayout<'tcx> {
fn is_llvm_immediate(&self) -> bool {
match self.abi {
layout::Abi::Scalar(_) | layout::Abi::Vector => true,
layout::Abi::Uninhabited |
layout::Abi::Scalar(_) |
layout::Abi::Vector => true,
layout::Abi::Aggregate { .. } => self.is_zst()
}
@ -230,7 +233,7 @@ impl<'tcx> LayoutLlvmExt<'tcx> for TyLayout<'tcx> {
let llty = if self.ty != normal_ty {
let mut layout = ccx.layout_of(normal_ty);
if let Some(v) = variant_index {
layout = layout.for_variant(v);
layout = layout.for_variant(ccx, v);
}
layout.llvm_type(ccx)
} else {

View File

@ -0,0 +1,18 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// compile-flags: -Z print-type-sizes
#![feature(never_type)]
pub fn main() {
let _x: Option<!> = None;
let _y: Result<u32, !> = Ok(42);
}

View File

@ -0,0 +1,5 @@
print-type-size type: `std::result::Result<u32, !>`: 4 bytes, alignment: 4 bytes
print-type-size variant `Ok`: 4 bytes
print-type-size field `.0`: 4 bytes
print-type-size type: `std::option::Option<!>`: 0 bytes, alignment: 1 bytes
print-type-size variant `None`: 0 bytes