diff --git a/src/librustc/ty/layout.rs b/src/librustc/ty/layout.rs index ac5e3c6fa70..9270057b544 100644 --- a/src/librustc/ty/layout.rs +++ b/src/librustc/ty/layout.rs @@ -488,7 +488,7 @@ impl<'a, 'gcx, 'tcx> Struct { for field in fields { if !self.sized { - bug!("Struct::compute: field #{} of `{}` comes after unsized field", + bug!("Struct::extend: field #{} of `{}` comes after unsized field", self.offset_after_field.len(), scapegoat); } @@ -623,6 +623,54 @@ impl<'a, 'gcx, 'tcx> Struct { } } +/// An untagged union. +#[derive(PartialEq, Eq, Hash, Debug)] +pub struct Union { + pub align: Align, + + pub min_size: Size, + + /// If true, no alignment padding is used. + pub packed: bool, +} + +impl<'a, 'gcx, 'tcx> Union { + pub fn new(dl: &TargetDataLayout, packed: bool) -> Union { + Union { + align: if packed { dl.i8_align } else { dl.aggregate_align }, + min_size: Size::from_bytes(0), + packed: packed, + } + } + + /// Extend the Struct with more fields. + pub fn extend(&mut self, dl: &TargetDataLayout, + fields: I, + scapegoat: Ty<'gcx>) + -> Result<(), LayoutError<'gcx>> + where I: Iterator>> { + for (index, field) in fields.enumerate() { + let field = field?; + if field.is_unsized() { + bug!("Union::extend: field #{} of `{}` is unsized", + index, scapegoat); + } + + if !self.packed { + self.align = self.align.max(field.align(dl)); + } + self.min_size = cmp::max(self.min_size, field.size(dl)); + } + + Ok(()) + } + + /// Get the size with trailing aligment padding. + pub fn stride(&self) -> Size { + self.min_size.abi_align(self.align) + } +} + /// The first half of a fat pointer. /// - For a trait object, this is the address of the box. /// - For a slice, this is the base address. @@ -690,6 +738,11 @@ pub enum Layout { non_zero: bool }, + /// Untagged unions. + UntaggedUnion { + variants: Union, + }, + /// General-case enums: for each case there is a struct, and they /// all start with a field for the discriminant. General { @@ -896,8 +949,14 @@ impl<'a, 'gcx, 'tcx> Layout { non_zero: Some(def.did) == tcx.lang_items.non_zero() } } - ty::TyUnion(..) => { - unimplemented_unions!(); + ty::TyUnion(def, substs) => { + let fields = def.struct_variant().fields.iter().map(|field| { + field.ty(tcx, substs).layout(infcx) + }); + let packed = tcx.lookup_packed(def.did); + let mut un = Union::new(dl, packed); + un.extend(dl, fields, ty)?; + UntaggedUnion { variants: un } } ty::TyEnum(def, substs) => { let hint = *tcx.lookup_repr_hints(def.did).get(0) @@ -1118,7 +1177,7 @@ impl<'a, 'gcx, 'tcx> Layout { pub fn is_unsized(&self) -> bool { match *self { Scalar {..} | Vector {..} | FatPointer {..} | - CEnum {..} | General {..} | + CEnum {..} | UntaggedUnion {..} | General {..} | RawNullablePointer {..} | StructWrappedNullablePointer {..} => false, @@ -1152,6 +1211,7 @@ impl<'a, 'gcx, 'tcx> Layout { CEnum { discr, .. } => Int(discr).size(dl), Array { size, .. } | General { size, .. } => size, + UntaggedUnion { ref variants } => variants.stride(), Univariant { ref variant, .. } | StructWrappedNullablePointer { nonnull: ref variant, .. } => { @@ -1191,6 +1251,7 @@ impl<'a, 'gcx, 'tcx> Layout { CEnum { discr, .. } => Int(discr).align(dl), Array { align, .. } | General { align, .. } => align, + UntaggedUnion { ref variants } => variants.align, Univariant { ref variant, .. } | StructWrappedNullablePointer { nonnull: ref variant, .. } => { @@ -1256,9 +1317,6 @@ impl<'a, 'gcx, 'tcx> SizeSkeleton<'gcx> { } } - ty::TyUnion(..) => { - unimplemented_unions!(); - } ty::TyStruct(def, substs) | ty::TyEnum(def, substs) => { // Only newtypes and enums w/ nullable pointer optimization. if def.variants.is_empty() || def.variants.len() > 2 { diff --git a/src/librustc_mir/hair/cx/expr.rs b/src/librustc_mir/hair/cx/expr.rs index 0469d44de4b..c8f660a2d9c 100644 --- a/src/librustc_mir/hair/cx/expr.rs +++ b/src/librustc_mir/hair/cx/expr.rs @@ -459,7 +459,7 @@ fn make_mirror_unadjusted<'a, 'gcx, 'tcx>(cx: &mut Cx<'a, 'gcx, 'tcx>, hir::ExprStruct(_, ref fields, ref base) => { match expr_ty.sty { - ty::TyStruct(adt, substs) => { + ty::TyStruct(adt, substs) | ty::TyUnion(adt, substs) => { let field_refs = field_refs(&adt.variants[0], fields); ExprKind::Adt { adt_def: adt, @@ -477,9 +477,6 @@ fn make_mirror_unadjusted<'a, 'gcx, 'tcx>(cx: &mut Cx<'a, 'gcx, 'tcx>, }) } } - ty::TyUnion(..) => { - unimplemented_unions!(); - } ty::TyEnum(adt, substs) => { match cx.tcx.expect_def(expr.id) { Def::Variant(enum_id, variant_id) => { diff --git a/src/librustc_trans/adt.rs b/src/librustc_trans/adt.rs index 069eef7895c..abbb9a5d4db 100644 --- a/src/librustc_trans/adt.rs +++ b/src/librustc_trans/adt.rs @@ -79,6 +79,8 @@ pub enum Repr<'tcx> { CEnum(IntType, Disr, Disr), // discriminant range (signedness based on the IntType) /// Single-case variants, and structs/tuples/records. Univariant(Struct<'tcx>), + /// Untagged unions. + UntaggedUnion(Union<'tcx>), /// General-case enums: for each case there is a struct, and they /// all start with a field for the discriminant. General(IntType, Vec>), @@ -121,6 +123,15 @@ pub struct Struct<'tcx> { pub fields: Vec>, } +/// For untagged unions. +#[derive(Eq, PartialEq, Debug)] +pub struct Union<'tcx> { + pub min_size: u64, + pub align: u32, + pub packed: bool, + pub fields: Vec>, +} + #[derive(Copy, Clone)] pub struct MaybeSizedValue { pub value: ValueRef, @@ -176,8 +187,12 @@ fn represent_type_uncached<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, Univariant(mk_struct(cx, &ftys[..], packed, t)) } - ty::TyUnion(..) => { - unimplemented_unions!(); + ty::TyUnion(def, substs) => { + let ftys = def.struct_variant().fields.iter().map(|field| { + monomorphize::field_ty(cx.tcx(), substs, field) + }).collect::>(); + let packed = cx.tcx().lookup_packed(def.did); + UntaggedUnion(mk_union(cx, &ftys[..], packed, t)) } ty::TyClosure(_, ref substs) => { Univariant(mk_struct(cx, &substs.upvar_tys, false, t)) @@ -482,6 +497,31 @@ fn mk_struct<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, } } +fn mk_union<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, + tys: &[Ty<'tcx>], packed: bool, + _scapegoat: Ty<'tcx>) + -> Union<'tcx> { + let mut min_size = 0; + let mut align = 0; + for llty in tys.iter().map(|&ty| type_of::sizing_type_of(cx, ty)) { + let field_size = machine::llsize_of_alloc(cx, llty); + if min_size < field_size { + min_size = field_size; + } + let field_align = machine::llalign_of_min(cx, llty); + if align < field_align { + align = field_align; + } + } + + Union { + min_size: min_size, + align: align, + packed: packed, + fields: tys.to_vec(), + } +} + #[derive(Debug)] struct IntBounds { slo: i64, @@ -646,7 +686,7 @@ pub fn incomplete_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, pub fn finish_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, r: &Repr<'tcx>, llty: &mut Type) { match *r { - CEnum(..) | General(..) | RawNullablePointer { .. } => { } + CEnum(..) | General(..) | UntaggedUnion(..) | RawNullablePointer { .. } => { } Univariant(ref st) | StructWrappedNullablePointer { nonnull: ref st, .. } => llty.set_struct_body(&struct_llfields(cx, st, false, false), st.packed) @@ -690,6 +730,34 @@ fn generic_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, } } } + UntaggedUnion(ref un) => { + // Use alignment-sized ints to fill all the union storage. + let (size, align) = (roundup(un.min_size, un.align), un.align); + + let align_s = align as u64; + assert_eq!(size % align_s, 0); // Ensure division in align_units comes out evenly + let align_units = size / align_s; + let fill_ty = match align_s { + 1 => Type::array(&Type::i8(cx), align_units), + 2 => Type::array(&Type::i16(cx), align_units), + 4 => Type::array(&Type::i32(cx), align_units), + 8 if machine::llalign_of_min(cx, Type::i64(cx)) == 8 => + Type::array(&Type::i64(cx), align_units), + a if a.count_ones() == 1 => Type::array(&Type::vector(&Type::i32(cx), a / 4), + align_units), + _ => bug!("unsupported union alignment: {}", align) + }; + match name { + None => { + TypeContext::direct(Type::struct_(cx, &[fill_ty], un.packed)) + } + Some(name) => { + let mut llty = Type::named_struct(cx, name); + llty.set_struct_body(&[fill_ty], un.packed); + TypeContext::direct(llty) + } + } + } General(ity, ref sts) => { // We need a representation that has: // * The alignment of the most-aligned field @@ -762,7 +830,7 @@ pub fn trans_switch<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, RawNullablePointer { .. } | StructWrappedNullablePointer { .. } => { (BranchKind::Switch, Some(trans_get_discr(bcx, r, scrutinee, None, range_assert))) } - Univariant(..) => { + Univariant(..) | UntaggedUnion(..) => { // N.B.: Univariant means <= 1 enum variants (*not* == 1 variants). (BranchKind::Single, None) } @@ -773,7 +841,7 @@ pub fn is_discr_signed<'tcx>(r: &Repr<'tcx>) -> bool { match *r { CEnum(ity, _, _) => ity.is_signed(), General(ity, _) => ity.is_signed(), - Univariant(..) => false, + Univariant(..) | UntaggedUnion(..) => false, RawNullablePointer { .. } => false, StructWrappedNullablePointer { .. } => false, } @@ -794,7 +862,7 @@ pub fn trans_get_discr<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, r: &Repr<'tcx>, load_discr(bcx, ity, ptr, Disr(0), Disr(cases.len() as u64 - 1), range_assert) } - Univariant(..) => C_u8(bcx.ccx(), 0), + Univariant(..) | UntaggedUnion(..) => C_u8(bcx.ccx(), 0), RawNullablePointer { nndiscr, nnty, .. } => { let cmp = if nndiscr == Disr(0) { IntEQ } else { IntNE }; let llptrty = type_of::sizing_type_of(bcx.ccx(), nnty); @@ -856,8 +924,8 @@ pub fn trans_case<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, r: &Repr, discr: Disr) General(ity, _) => { C_integral(ll_inttype(bcx.ccx(), ity), discr.0, true) } - Univariant(..) => { - bug!("no cases for univariants or structs") + Univariant(..) | UntaggedUnion(..) => { + bug!("no cases for univariants, structs or unions") } RawNullablePointer { .. } | StructWrappedNullablePointer { .. } => { @@ -884,6 +952,9 @@ pub fn trans_set_discr<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, r: &Repr<'tcx>, Univariant(_) => { assert_eq!(discr, Disr(0)); } + UntaggedUnion(..) => { + assert_eq!(discr, Disr(0)); + } RawNullablePointer { nndiscr, nnty, ..} => { if discr != nndiscr { let llptrty = type_of::sizing_type_of(bcx.ccx(), nnty); @@ -939,6 +1010,11 @@ pub fn trans_field_ptr_builder<'blk, 'tcx>(bcx: &BlockAndBuilder<'blk, 'tcx>, General(_, ref cases) => { struct_field_ptr(bcx, &cases[discr.0 as usize], val, ix + 1, true) } + UntaggedUnion(ref un) => { + let ty = type_of::in_memory_type_of(bcx.ccx(), un.fields[ix]); + if bcx.is_unreachable() { return C_undef(ty.ptr_to()); } + bcx.pointercast(val.value, ty.ptr_to()) + } RawNullablePointer { nndiscr, ref nullfields, .. } | StructWrappedNullablePointer { nndiscr, ref nullfields, .. } if discr != nndiscr => { // The unit-like case might have a nonzero number of unit-like fields. @@ -1100,6 +1176,9 @@ pub fn trans_const<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>, r: &Repr<'tcx>, discr contents.extend_from_slice(&[padding(ccx, max_sz - case.size)]); C_struct(ccx, &contents[..], false) } + UntaggedUnion(..) => { + unimplemented_unions!(); + } Univariant(ref st) => { assert_eq!(discr, Disr(0)); let contents = build_const_struct(ccx, st, vals); @@ -1211,6 +1290,7 @@ pub fn const_get_field(r: &Repr, val: ValueRef, _discr: Disr, match *r { CEnum(..) => bug!("element access in C-like enum const"), Univariant(..) => const_struct_field(val, ix), + UntaggedUnion(..) => const_struct_field(val, 0), General(..) => const_struct_field(val, ix + 1), RawNullablePointer { .. } => { assert_eq!(ix, 0); diff --git a/src/librustc_trans/debuginfo/metadata.rs b/src/librustc_trans/debuginfo/metadata.rs index f30880ac9be..bd67a215d65 100644 --- a/src/librustc_trans/debuginfo/metadata.rs +++ b/src/librustc_trans/debuginfo/metadata.rs @@ -1302,6 +1302,9 @@ impl<'tcx> EnumMemberDescriptionFactory<'tcx> { ] } } + adt::UntaggedUnion(..) => { + unimplemented_unions!(); + } adt::RawNullablePointer { nndiscr: non_null_variant_index, nnty, .. } => { // As far as debuginfo is concerned, the pointer this enum // represents is still wrapped in a struct. This is to make the @@ -1616,7 +1619,7 @@ fn prepare_enum_metadata<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, }, adt::RawNullablePointer { .. } | adt::StructWrappedNullablePointer { .. } | - adt::Univariant(..) => None, + adt::Univariant(..) | adt::UntaggedUnion(..) => None, adt::General(inttype, _) => Some(discriminant_type_metadata(inttype)), }; diff --git a/src/test/run-pass/union-basic.rs b/src/test/run-pass/union-basic.rs new file mode 100644 index 00000000000..474c8b4b181 --- /dev/null +++ b/src/test/run-pass/union-basic.rs @@ -0,0 +1,47 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(untagged_unions)] + +use std::mem::{size_of, align_of, zeroed}; + +union U { + a: u8, +} + +union U64 { + a: u64, +} + +union W { + a: u8, + b: u64, +} + +fn main() { + assert_eq!(size_of::(), 1); + assert_eq!(size_of::(), 8); + assert_eq!(size_of::(), 8); + assert_eq!(align_of::(), 1); + assert_eq!(align_of::(), align_of::()); + assert_eq!(align_of::(), align_of::()); + + let u = U { a: 10 }; + assert_eq!(u.a, 10); + let U { a } = u; + assert_eq!(a, 10); + + let mut w: W = unsafe { zeroed() }; + assert_eq!(w.a, 0); + assert_eq!(w.b, 0); + // w.a = 1; + // assert_eq!(w.a, 0); + // assert_eq!(w.b, 0); +}