From 4358e35fda66ab7a00215c7f9d50e7c6dc9b801b Mon Sep 17 00:00:00 2001 From: Cameron Hart Date: Sun, 15 Jan 2017 09:49:29 +1100 Subject: [PATCH 1/4] Implementation of repr struct alignment RFC 1358. The main changes around rustc::ty::Layout::struct and rustc_trans:adt: * Added primitive_align field which stores alignment before repr align * Always emit field padding when generating the LLVM struct fields * Added methods for adjusting field indexes from the layout index to the LLVM struct field index The main user of this information is rustc_trans::adt::struct_llfields which determines the LLVM fields to be used by LLVM, including padding fields. --- src/librustc/diagnostics.rs | 3 +- src/librustc/hir/check_attr.rs | 17 ++ src/librustc/ty/layout.rs | 89 +++++++- src/librustc/ty/mod.rs | 11 +- src/librustc_trans/abi.rs | 2 +- src/librustc_trans/adt.rs | 82 ++++++-- src/librustc_trans/builder.rs | 12 +- src/librustc_trans/intrinsic.rs | 2 +- src/librustc_trans/mir/block.rs | 12 +- src/librustc_trans/mir/lvalue.rs | 17 +- src/librustc_trans/mir/mod.rs | 2 +- src/librustc_trans/mir/operand.rs | 36 ++-- src/librustc_trans/mir/rvalue.rs | 16 +- src/librustc_trans/type_of.rs | 10 + src/librustc_typeck/check/mod.rs | 47 +++++ src/librustc_typeck/diagnostics.rs | 1 + src/libsyntax/attr.rs | 50 ++++- src/libsyntax/diagnostic_list.rs | 2 +- src/libsyntax_ext/deriving/generic/mod.rs | 2 +- src/test/compile-fail/attr-usage-repr.rs | 4 + .../compile-fail/conflicting-repr-hints.rs | 8 +- src/test/compile-fail/repr-align.rs | 22 ++ .../repr-packed-contains-align.rs | 24 +++ src/test/run-pass/align-struct.rs | 195 ++++++++++++++++++ src/test/ui/print_type_sizes/repr-align.rs | 42 ++++ .../ui/print_type_sizes/repr-align.stdout | 16 ++ 26 files changed, 646 insertions(+), 78 deletions(-) create mode 100644 src/test/compile-fail/repr-align.rs create mode 100644 src/test/compile-fail/repr-packed-contains-align.rs create mode 100644 src/test/run-pass/align-struct.rs create mode 100644 src/test/ui/print_type_sizes/repr-align.rs create mode 100644 src/test/ui/print_type_sizes/repr-align.stdout diff --git a/src/librustc/diagnostics.rs b/src/librustc/diagnostics.rs index 618561f3b0257..2851191dc141a 100644 --- a/src/librustc/diagnostics.rs +++ b/src/librustc/diagnostics.rs @@ -1847,5 +1847,6 @@ register_diagnostics! { E0489, // type/lifetime parameter not in scope here E0490, // a value of type `..` is borrowed for too long E0495, // cannot infer an appropriate lifetime due to conflicting requirements - E0566 // conflicting representation hints + E0566, // conflicting representation hints + E0587, // conflicting packed and align representation hints } diff --git a/src/librustc/hir/check_attr.rs b/src/librustc/hir/check_attr.rs index 54ae947214091..bf292ccb8d86d 100644 --- a/src/librustc/hir/check_attr.rs +++ b/src/librustc/hir/check_attr.rs @@ -57,6 +57,9 @@ impl<'a> CheckAttrVisitor<'a> { }; let mut conflicting_reprs = 0; + let mut found_packed = false; + let mut found_align = false; + for word in words { let name = match word.name() { @@ -84,6 +87,7 @@ impl<'a> CheckAttrVisitor<'a> { ("attribute should be applied to struct or union", "a struct or union") } else { + found_packed = true; continue } } @@ -96,6 +100,15 @@ impl<'a> CheckAttrVisitor<'a> { continue } } + "align" => { + found_align = true; + if target != Target::Struct { + ("attribute should be applied to struct", + "a struct") + } else { + continue + } + } "i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" | "isize" | "usize" => { @@ -117,6 +130,10 @@ impl<'a> CheckAttrVisitor<'a> { span_warn!(self.sess, attr.span, E0566, "conflicting representation hints"); } + if found_align && found_packed { + struct_span_err!(self.sess, attr.span, E0587, + "conflicting packed and align representation hints").emit(); + } } fn check_attribute(&self, attr: &ast::Attribute, target: Target) { diff --git a/src/librustc/ty/layout.rs b/src/librustc/ty/layout.rs index df60eee8c0243..6a206640b3baa 100644 --- a/src/librustc/ty/layout.rs +++ b/src/librustc/ty/layout.rs @@ -548,8 +548,12 @@ pub type FieldPath = Vec; /// A structure, a product type in ADT terms. #[derive(PartialEq, Eq, Hash, Debug)] pub struct Struct { + /// Maximum alignment of fields and repr alignment. pub align: Align, + /// Primitive alignment of fields without repr alignment. + pub primitive_align: Align, + /// If true, no alignment padding is used. pub packed: bool, @@ -583,10 +587,20 @@ impl<'a, 'gcx, 'tcx> Struct { fn new(dl: &TargetDataLayout, fields: &Vec<&'a Layout>, repr: &ReprOptions, kind: StructKind, scapegoat: Ty<'gcx>) -> Result> { - let packed = repr.packed(); + if repr.packed() && repr.align > 0 { + bug!("Struct cannot be packed and aligned"); + } + + let align = if repr.packed() { + dl.i8_align + } else { + dl.aggregate_align + }; + let mut ret = Struct { - align: if packed { dl.i8_align } else { dl.aggregate_align }, - packed: packed, + align: align, + primitive_align: align, + packed: repr.packed(), sized: true, offsets: vec![], memory_index: vec![], @@ -660,7 +674,9 @@ impl<'a, 'gcx, 'tcx> Struct { // Invariant: offset < dl.obj_size_bound() <= 1<<61 if !ret.packed { let align = field.align(dl); + let primitive_align = field.primitive_align(dl); ret.align = ret.align.max(align); + ret.primitive_align = ret.primitive_align.max(primitive_align); offset = offset.abi_align(align); } @@ -671,6 +687,11 @@ impl<'a, 'gcx, 'tcx> Struct { .map_or(Err(LayoutError::SizeOverflow(scapegoat)), Ok)?; } + if repr.align > 0 { + let repr_align = repr.align as u64; + ret.align = ret.align.max(Align::from_bytes(repr_align, repr_align).unwrap()); + debug!("Struct::new repr_align: {:?}", repr_align); + } debug!("Struct::new min_size: {:?}", offset); ret.min_size = offset; @@ -836,12 +857,23 @@ impl<'a, 'gcx, 'tcx> Struct { } Ok(None) } + + pub fn over_align(&self) -> Option { + let align = self.align.abi(); + let primitive_align = self.primitive_align.abi(); + if align > primitive_align { + Some(align as u32) + } else { + None + } + } } /// An untagged union. #[derive(PartialEq, Eq, Hash, Debug)] pub struct Union { pub align: Align, + pub primitive_align: Align, pub min_size: Size, @@ -851,8 +883,10 @@ pub struct Union { impl<'a, 'gcx, 'tcx> Union { fn new(dl: &TargetDataLayout, packed: bool) -> Union { + let align = if packed { dl.i8_align } else { dl.aggregate_align }; Union { - align: if packed { dl.i8_align } else { dl.aggregate_align }, + align: align, + primitive_align: align, min_size: Size::from_bytes(0), packed: packed, } @@ -875,6 +909,7 @@ impl<'a, 'gcx, 'tcx> Union { if !self.packed { self.align = self.align.max(field.align(dl)); + self.primitive_align = self.primitive_align.max(field.primitive_align(dl)); } self.min_size = cmp::max(self.min_size, field.size(dl)); } @@ -888,6 +923,16 @@ impl<'a, 'gcx, 'tcx> Union { pub fn stride(&self) -> Size { self.min_size.abi_align(self.align) } + + pub fn over_align(&self) -> Option { + let align = self.align.abi(); + let primitive_align = self.primitive_align.abi(); + if align > primitive_align { + Some(align as u32) + } else { + None + } + } } /// The first half of a fat pointer. @@ -924,6 +969,7 @@ pub enum Layout { /// If true, the size is exact, otherwise it's only a lower bound. sized: bool, align: Align, + primitive_align: Align, element_size: Size, count: u64 }, @@ -970,7 +1016,8 @@ pub enum Layout { discr: Integer, variants: Vec, size: Size, - align: Align + align: Align, + primitive_align: Align, }, /// Two cases distinguished by a nullable pointer: the case with discriminant @@ -1118,6 +1165,7 @@ impl<'a, 'gcx, 'tcx> Layout { Array { sized: true, align: element.align(dl), + primitive_align: element.primitive_align(dl), element_size: element_size, count: count } @@ -1127,6 +1175,7 @@ impl<'a, 'gcx, 'tcx> Layout { Array { sized: false, align: element.align(dl), + primitive_align: element.primitive_align(dl), element_size: element.size(dl), count: 0 } @@ -1135,6 +1184,7 @@ impl<'a, 'gcx, 'tcx> Layout { Array { sized: false, align: dl.i8_align, + primitive_align: dl.i8_align, element_size: Size::from_bytes(1), count: 0 } @@ -1340,6 +1390,7 @@ impl<'a, 'gcx, 'tcx> Layout { assert!(discr_max >= 0); let (min_ity, _) = Integer::repr_discr(tcx, ty, &def.repr, 0, discr_max); let mut align = dl.aggregate_align; + let mut primitive_align = dl.aggregate_align; let mut size = Size::from_bytes(0); // We're interested in the smallest alignment, so start large. @@ -1369,6 +1420,7 @@ impl<'a, 'gcx, 'tcx> Layout { } size = cmp::max(size, st.min_size); align = align.max(st.align); + primitive_align = primitive_align.max(st.primitive_align); Ok(st) }).collect::, _>>()?; @@ -1435,7 +1487,8 @@ impl<'a, 'gcx, 'tcx> Layout { discr: ity, variants: variants, size: size, - align: align + align: align, + primitive_align: primitive_align } } @@ -1557,6 +1610,30 @@ impl<'a, 'gcx, 'tcx> Layout { } } + /// Returns alignment before repr alignment is applied + pub fn primitive_align(&self, dl: &TargetDataLayout) -> Align { + match *self { + Array { primitive_align, .. } | General { primitive_align, .. } => primitive_align, + Univariant { ref variant, .. } | + StructWrappedNullablePointer { nonnull: ref variant, .. } => { + variant.primitive_align + }, + + _ => self.align(dl) + } + } + + /// Returns repr alignment if it is greater than the primitive alignment. + pub fn over_align(&self, dl: &TargetDataLayout) -> Option { + let align = self.align(dl); + let primitive_align = self.primitive_align(dl); + if align.abi() > primitive_align.abi() { + Some(align.abi() as u32) + } else { + None + } + } + pub fn field_offset(&self, cx: C, i: usize, diff --git a/src/librustc/ty/mod.rs b/src/librustc/ty/mod.rs index ab1a06aeacd18..db8b093e369e0 100644 --- a/src/librustc/ty/mod.rs +++ b/src/librustc/ty/mod.rs @@ -37,6 +37,7 @@ use serialize::{self, Encodable, Encoder}; use std::borrow::Cow; use std::cell::{Cell, RefCell, Ref}; use std::collections::BTreeMap; +use std::cmp; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::rc::Rc; @@ -1464,10 +1465,12 @@ impl_stable_hash_for!(struct ReprFlags { #[derive(Copy, Clone, Eq, PartialEq, RustcEncodable, RustcDecodable, Default)] pub struct ReprOptions { pub int: Option, + pub align: u16, pub flags: ReprFlags, } impl_stable_hash_for!(struct ReprOptions { + align, int, flags }); @@ -1476,7 +1479,7 @@ impl ReprOptions { pub fn new(tcx: TyCtxt, did: DefId) -> ReprOptions { let mut flags = ReprFlags::empty(); let mut size = None; - + let mut max_align = 0; for attr in tcx.get_attrs(did).iter() { for r in attr::find_repr_attrs(tcx.sess.diagnostic(), attr) { flags.insert(match r { @@ -1487,6 +1490,10 @@ impl ReprOptions { size = Some(i); ReprFlags::empty() }, + attr::ReprAlign(align) => { + max_align = cmp::max(align, max_align); + ReprFlags::empty() + }, }); } } @@ -1500,7 +1507,7 @@ impl ReprOptions { if !tcx.consider_optimizing(|| format!("Reorder fields of {:?}", tcx.item_path_str(did))) { flags.insert(ReprFlags::IS_LINEAR); } - ReprOptions { int: size, flags: flags } + ReprOptions { int: size, align: max_align, flags: flags } } #[inline] diff --git a/src/librustc_trans/abi.rs b/src/librustc_trans/abi.rs index c4fdc46d030c9..efe4acfa9e072 100644 --- a/src/librustc_trans/abi.rs +++ b/src/librustc_trans/abi.rs @@ -553,7 +553,7 @@ impl<'a, 'tcx> ArgType<'tcx> { // bitcasting to the struct type yields invalid cast errors. // We instead thus allocate some scratch space... - let llscratch = bcx.alloca(ty, "abi_cast"); + let llscratch = bcx.alloca(ty, "abi_cast", None); base::Lifetime::Start.call(bcx, llscratch); // ...where we first store the value... diff --git a/src/librustc_trans/adt.rs b/src/librustc_trans/adt.rs index 87ca410dece0d..3c73aba7cd7ed 100644 --- a/src/librustc_trans/adt.rs +++ b/src/librustc_trans/adt.rs @@ -90,12 +90,12 @@ pub fn compute_fields<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, t: Ty<'tcx>, /// and fill in the actual contents in a second pass to prevent /// unbounded recursion; see also the comments in `trans::type_of`. pub fn type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, t: Ty<'tcx>) -> Type { - generic_type_of(cx, t, None, false, false) + generic_type_of(cx, t, None, false) } pub fn incomplete_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, t: Ty<'tcx>, name: &str) -> Type { - generic_type_of(cx, t, Some(name), false, false) + generic_type_of(cx, t, Some(name), false) } pub fn finish_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, @@ -114,7 +114,7 @@ pub fn finish_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, _ => unreachable!() }; let fields = compute_fields(cx, t, nonnull_variant_index as usize, true); - llty.set_struct_body(&struct_llfields(cx, &fields, nonnull_variant, false, false), + llty.set_struct_body(&struct_llfields(cx, &fields, nonnull_variant, false), packed) }, _ => bug!("This function cannot handle {} with layout {:#?}", t, l) @@ -124,11 +124,9 @@ pub fn finish_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, fn generic_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, t: Ty<'tcx>, name: Option<&str>, - sizing: bool, - dst: bool) -> Type { + sizing: bool) -> Type { let l = cx.layout_of(t); - debug!("adt::generic_type_of t: {:?} name: {:?} sizing: {} dst: {}", - t, name, sizing, dst); + debug!("adt::generic_type_of t: {:?} name: {:?} sizing: {}", t, name, sizing); match *l { layout::CEnum { discr, .. } => Type::from_integer(cx, discr), layout::RawNullablePointer { nndiscr, .. } => { @@ -148,7 +146,7 @@ fn generic_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, let fields = compute_fields(cx, t, nndiscr as usize, false); match name { None => { - Type::struct_(cx, &struct_llfields(cx, &fields, nonnull, sizing, dst), + Type::struct_(cx, &struct_llfields(cx, &fields, nonnull, sizing), nonnull.packed) } Some(name) => { @@ -163,7 +161,7 @@ fn generic_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, let fields = compute_fields(cx, t, 0, true); match name { None => { - let fields = struct_llfields(cx, &fields, &variant, sizing, dst); + let fields = struct_llfields(cx, &fields, &variant, sizing); Type::struct_(cx, &fields, variant.packed) } Some(name) => { @@ -245,15 +243,65 @@ fn union_fill(cx: &CrateContext, size: u64, align: u64) -> Type { } -fn struct_llfields<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, fields: &Vec>, +// Double index to account for padding (FieldPath already uses `Struct::memory_index`) +fn struct_llfields_path(discrfield: &layout::FieldPath) -> Vec { + discrfield.iter().map(|&i| (i as usize) << 1).collect::>() +} + + +// Lookup `Struct::memory_index` and double it to account for padding +pub fn struct_llfields_index(variant: &layout::Struct, index: usize) -> usize { + (variant.memory_index[index] as usize) << 1 +} + + +pub fn struct_llfields<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, field_tys: &Vec>, variant: &layout::Struct, - sizing: bool, _dst: bool) -> Vec { - let fields = variant.field_index_by_increasing_offset().map(|i| fields[i as usize]); + sizing: bool) -> Vec { if sizing { - bug!() + bug!(); + } + debug!("struct_llfields: variant: {:?}", variant); + let mut first_field = true; + let mut min_offset = 0; + let mut result: Vec = Vec::with_capacity(field_tys.len() * 2); + let field_iter = variant.field_index_by_increasing_offset().map(|i| { + (i, field_tys[i as usize], variant.offsets[i as usize].bytes()) }); + for (index, ty, target_offset) in field_iter.filter( + |&(_, ty, _)| !sizing || cx.shared().type_is_sized(ty)) { + if first_field { + debug!("struct_llfields: {} ty: {} min_offset: {} target_offset: {}", + index, ty, min_offset, target_offset); + first_field = false; + } else { + assert!(target_offset >= min_offset); + let padding_bytes = if variant.packed { 0 } else { target_offset - min_offset }; + result.push(Type::array(&Type::i8(cx), padding_bytes)); + debug!("struct_llfields: {} ty: {} pad_bytes: {} min_offset: {} target_offset: {}", + index, ty, padding_bytes, min_offset, target_offset); + } + let llty = type_of::in_memory_type_of(cx, ty); + result.push(llty); + let layout = cx.layout_of(ty); + let target_size = layout.size(&cx.tcx().data_layout).bytes(); + min_offset = target_offset + target_size; + } + if variant.sized && !field_tys.is_empty() { + if variant.stride().bytes() < min_offset { + bug!("variant: {:?} stride: {} min_offset: {}", variant, variant.stride().bytes(), + min_offset); + } + let padding_bytes = variant.stride().bytes() - min_offset; + debug!("struct_llfields: pad_bytes: {} min_offset: {} min_size: {} stride: {}\n", + padding_bytes, min_offset, variant.min_size.bytes(), variant.stride().bytes()); + result.push(Type::array(&Type::i8(cx), padding_bytes)); + assert!(result.len() == (field_tys.len() * 2)); } else { - fields.map(|ty| type_of::in_memory_type_of(cx, ty)).collect() + debug!("struct_llfields: min_offset: {} min_size: {} stride: {}\n", + min_offset, variant.min_size.bytes(), variant.stride().bytes()); } + + result } pub fn is_discr_signed<'tcx>(l: &layout::Layout) -> bool { @@ -309,8 +357,8 @@ fn struct_wrapped_nullable_bitdiscr( scrutinee: ValueRef, alignment: Alignment, ) -> ValueRef { - let llptrptr = bcx.gepi(scrutinee, - &discrfield.iter().map(|f| *f as usize).collect::>()); + let path = struct_llfields_path(discrfield); + let llptrptr = bcx.gepi(scrutinee, &path); let llptr = bcx.load(llptrptr, alignment.to_align()); let cmp = if nndiscr == 0 { IntEQ } else { IntNE }; bcx.icmp(cmp, llptr, C_null(val_ty(llptr))) @@ -380,7 +428,7 @@ pub fn trans_set_discr<'a, 'tcx>(bcx: &Builder<'a, 'tcx>, t: Ty<'tcx>, val: Valu let align = C_i32(bcx.ccx, nonnull.align.abi() as i32); base::call_memset(bcx, llptr, fill_byte, size, align, false); } else { - let path = discrfield.iter().map(|&i| i as usize).collect::>(); + let path = struct_llfields_path(discrfield); let llptrptr = bcx.gepi(val, &path); let llptrty = val_ty(llptrptr).element_type(); bcx.store(C_null(llptrty), llptrptr, None); diff --git a/src/librustc_trans/builder.rs b/src/librustc_trans/builder.rs index 8b1010d89fd9f..5103ca5c5e109 100644 --- a/src/librustc_trans/builder.rs +++ b/src/librustc_trans/builder.rs @@ -477,24 +477,28 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } } - pub fn alloca(&self, ty: Type, name: &str) -> ValueRef { + pub fn alloca(&self, ty: Type, name: &str, align: Option) -> ValueRef { let builder = Builder::with_ccx(self.ccx); builder.position_at_start(unsafe { llvm::LLVMGetFirstBasicBlock(self.llfn()) }); - builder.dynamic_alloca(ty, name) + builder.dynamic_alloca(ty, name, align) } - pub fn dynamic_alloca(&self, ty: Type, name: &str) -> ValueRef { + pub fn dynamic_alloca(&self, ty: Type, name: &str, align: Option) -> ValueRef { self.count_insn("alloca"); unsafe { - if name.is_empty() { + let alloca = if name.is_empty() { llvm::LLVMBuildAlloca(self.llbuilder, ty.to_ref(), noname()) } else { let name = CString::new(name).unwrap(); llvm::LLVMBuildAlloca(self.llbuilder, ty.to_ref(), name.as_ptr()) + }; + if let Some(align) = align { + llvm::LLVMSetAlignment(alloca, align as c_uint); } + alloca } } diff --git a/src/librustc_trans/intrinsic.rs b/src/librustc_trans/intrinsic.rs index 7077eade61182..54e20f590c671 100644 --- a/src/librustc_trans/intrinsic.rs +++ b/src/librustc_trans/intrinsic.rs @@ -778,7 +778,7 @@ fn trans_msvc_try<'a, 'tcx>(bcx: &Builder<'a, 'tcx>, // // More information can be found in libstd's seh.rs implementation. let i64p = Type::i64(ccx).ptr_to(); - let slot = bcx.alloca(i64p, "slot"); + let slot = bcx.alloca(i64p, "slot", None); bcx.invoke(func, &[data], normal.llbb(), catchswitch.llbb(), None); diff --git a/src/librustc_trans/mir/block.rs b/src/librustc_trans/mir/block.rs index 0f5a38ac7f6b8..d94d7f4430bf0 100644 --- a/src/librustc_trans/mir/block.rs +++ b/src/librustc_trans/mir/block.rs @@ -15,6 +15,7 @@ use rustc::ty::{self, TypeFoldable}; use rustc::ty::layout::{self, LayoutTyper}; use rustc::mir; use abi::{Abi, FnType, ArgType}; +use adt; use base::{self, Lifetime}; use callee; use builder::Builder; @@ -177,7 +178,7 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> { }; let llslot = match op.val { Immediate(_) | Pair(..) => { - let llscratch = bcx.alloca(ret.memory_ty(bcx.ccx), "ret"); + let llscratch = bcx.alloca(ret.memory_ty(bcx.ccx), "ret", None); self.store_operand(&bcx, llscratch, None, op); llscratch } @@ -630,7 +631,7 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> { let (mut llval, align, by_ref) = match op.val { Immediate(_) | Pair(..) => { if arg.is_indirect() || arg.cast.is_some() { - let llscratch = bcx.alloca(arg.memory_ty(bcx.ccx), "arg"); + let llscratch = bcx.alloca(arg.memory_ty(bcx.ccx), "arg", None); self.store_operand(bcx, llscratch, None, op); (llscratch, Alignment::AbiAligned, true) } else { @@ -642,7 +643,7 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> { // think that ATM (Rust 1.16) we only pass temporaries, but we shouldn't // have scary latent bugs around. - let llscratch = bcx.alloca(arg.memory_ty(bcx.ccx), "arg"); + let llscratch = bcx.alloca(arg.memory_ty(bcx.ccx), "arg", None); base::memcpy_ty(bcx, llscratch, llval, op.ty, Some(1)); (llscratch, Alignment::AbiAligned, true) } @@ -711,7 +712,8 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> { bug!("Not a tuple."); }; for (n, &ty) in arg_types.iter().enumerate() { - let mut elem = bcx.extract_value(llval, v.memory_index[n] as usize); + let mut elem = bcx.extract_value( + llval, adt::struct_llfields_index(v, n)); // Truncate bools to i1, if needed if ty.is_bool() && common::val_ty(elem) != Type::i1(bcx.ccx) { elem = bcx.trunc(elem, Type::i1(bcx.ccx)); @@ -750,7 +752,7 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> { slot } else { let llretty = Type::struct_(ccx, &[Type::i8p(ccx), Type::i32(ccx)], false); - let slot = bcx.alloca(llretty, "personalityslot"); + let slot = bcx.alloca(llretty, "personalityslot", None); self.llpersonalityslot = Some(slot); slot } diff --git a/src/librustc_trans/mir/lvalue.rs b/src/librustc_trans/mir/lvalue.rs index fc889604ab88e..9a461e4aafc23 100644 --- a/src/librustc_trans/mir/lvalue.rs +++ b/src/librustc_trans/mir/lvalue.rs @@ -97,7 +97,8 @@ impl<'a, 'tcx> LvalueRef<'tcx> { pub fn alloca(bcx: &Builder<'a, 'tcx>, ty: Ty<'tcx>, name: &str) -> LvalueRef<'tcx> { debug!("alloca({:?}: {:?})", name, ty); - let tmp = bcx.alloca(type_of::type_of(bcx.ccx, ty), name); + let tmp = bcx.alloca( + type_of::type_of(bcx.ccx, ty), name, bcx.ccx.over_align_of(ty)); assert!(!ty.has_param_types()); Self::new_sized_ty(tmp, ty, Alignment::AbiAligned) } @@ -131,11 +132,9 @@ impl<'a, 'tcx> LvalueRef<'tcx> { let alignment = self.alignment | Alignment::from_packed(st.packed); + let llfields = adt::struct_llfields(ccx, fields, st, false); let ptr_val = if needs_cast { - let fields = st.field_index_by_increasing_offset().map(|i| { - type_of::in_memory_type_of(ccx, fields[i]) - }).collect::>(); - let real_ty = Type::struct_(ccx, &fields[..], st.packed); + let real_ty = Type::struct_(ccx, &llfields[..], st.packed); bcx.pointercast(self.llval, real_ty.ptr_to()) } else { self.llval @@ -147,14 +146,16 @@ impl<'a, 'tcx> LvalueRef<'tcx> { // * Field is sized - pointer is properly aligned already if st.offsets[ix] == layout::Size::from_bytes(0) || st.packed || bcx.ccx.shared().type_is_sized(fty) { - return (bcx.struct_gep(ptr_val, st.memory_index[ix] as usize), alignment); + return (bcx.struct_gep( + ptr_val, adt::struct_llfields_index(st, ix)), alignment); } // If the type of the last field is [T] or str, then we don't need to do // any adjusments match fty.sty { ty::TySlice(..) | ty::TyStr => { - return (bcx.struct_gep(ptr_val, st.memory_index[ix] as usize), alignment); + return (bcx.struct_gep( + ptr_val, adt::struct_llfields_index(st, ix)), alignment); } _ => () } @@ -163,7 +164,7 @@ impl<'a, 'tcx> LvalueRef<'tcx> { if !self.has_extra() { debug!("Unsized field `{}`, of `{:?}` has no metadata for adjustment", ix, Value(ptr_val)); - return (bcx.struct_gep(ptr_val, ix), alignment); + return (bcx.struct_gep(ptr_val, adt::struct_llfields_index(st, ix)), alignment); } // We need to get the pointer manually now. diff --git a/src/librustc_trans/mir/mod.rs b/src/librustc_trans/mir/mod.rs index 3d8c5085462a8..d39a91405c185 100644 --- a/src/librustc_trans/mir/mod.rs +++ b/src/librustc_trans/mir/mod.rs @@ -524,7 +524,7 @@ fn arg_local_refs<'a, 'tcx>(bcx: &Builder<'a, 'tcx>, // doesn't actually strip the offset when splitting the closure // environment into its components so it ends up out of bounds. let env_ptr = if !env_ref { - let alloc = bcx.alloca(common::val_ty(llval), "__debuginfo_env_ptr"); + let alloc = bcx.alloca(common::val_ty(llval), "__debuginfo_env_ptr", None); bcx.store(llval, alloc, None); alloc } else { diff --git a/src/librustc_trans/mir/operand.rs b/src/librustc_trans/mir/operand.rs index c31142323c85f..6889b5064b649 100644 --- a/src/librustc_trans/mir/operand.rs +++ b/src/librustc_trans/mir/operand.rs @@ -15,6 +15,7 @@ use rustc::mir; use rustc::mir::tcx::LvalueTy; use rustc_data_structures::indexed_vec::Idx; +use adt; use base; use common::{self, CrateContext, C_null}; use builder::Builder; @@ -134,6 +135,12 @@ impl<'a, 'tcx> OperandRef<'tcx> { if common::val_ty(elem) == Type::i1(bcx.ccx) { elem = bcx.zext(elem, Type::i8(bcx.ccx)); } + let layout = bcx.ccx.layout_of(self.ty); + let i = if let Layout::Univariant { ref variant, .. } = *layout { + adt::struct_llfields_index(variant, i) + } else { + i + }; llpair = bcx.insert_value(llpair, elem, i); } self.val = OperandValue::Immediate(llpair); @@ -183,14 +190,17 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> { let (lldata, llextra) = base::load_fat_ptr(bcx, llval, align, ty); OperandValue::Pair(lldata, llextra) } else if common::type_is_imm_pair(bcx.ccx, ty) { - let f_align = match *bcx.ccx.layout_of(ty) { - Layout::Univariant { ref variant, .. } => - Alignment::from_packed(variant.packed) | align, - _ => align + let (ix0, ix1, f_align) = match *bcx.ccx.layout_of(ty) { + Layout::Univariant { ref variant, .. } => { + (adt::struct_llfields_index(variant, 0), + adt::struct_llfields_index(variant, 1), + Alignment::from_packed(variant.packed) | align) + }, + _ => (0, 1, align) }; let [a_ty, b_ty] = common::type_pair_fields(bcx.ccx, ty).unwrap(); - let a_ptr = bcx.struct_gep(llval, 0); - let b_ptr = bcx.struct_gep(llval, 1); + let a_ptr = bcx.struct_gep(llval, ix0); + let b_ptr = bcx.struct_gep(llval, ix1); OperandValue::Pair( base::load_ty(bcx, a_ptr, f_align, a_ty), @@ -302,17 +312,19 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> { bcx.store(base::from_immediate(bcx, s), lldest, align); } OperandValue::Pair(a, b) => { - let f_align = match *bcx.ccx.layout_of(operand.ty) { - Layout::Univariant { ref variant, .. } if variant.packed => { - Some(1) + let (ix0, ix1, f_align) = match *bcx.ccx.layout_of(operand.ty) { + Layout::Univariant { ref variant, .. } => { + (adt::struct_llfields_index(variant, 0), + adt::struct_llfields_index(variant, 1), + if variant.packed { Some(1) } else { None }) } - _ => align + _ => (0, 1, align) }; let a = base::from_immediate(bcx, a); let b = base::from_immediate(bcx, b); - bcx.store(a, bcx.struct_gep(lldest, 0), f_align); - bcx.store(b, bcx.struct_gep(lldest, 1), f_align); + bcx.store(a, bcx.struct_gep(lldest, ix0), f_align); + bcx.store(b, bcx.struct_gep(lldest, ix1), f_align); } } } diff --git a/src/librustc_trans/mir/rvalue.rs b/src/librustc_trans/mir/rvalue.rs index 98e9008f829f6..de1c1e492f39b 100644 --- a/src/librustc_trans/mir/rvalue.rs +++ b/src/librustc_trans/mir/rvalue.rs @@ -130,10 +130,12 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> { _ => { // If this is a tuple or closure, we need to translate GEP indices. let layout = bcx.ccx.layout_of(dest.ty.to_ty(bcx.tcx())); - let translation = if let Layout::Univariant { ref variant, .. } = *layout { - Some(&variant.memory_index) - } else { - None + let get_memory_index = |i| { + if let Layout::Univariant { ref variant, .. } = *layout { + adt::struct_llfields_index(variant, i) + } else { + i + } }; let alignment = dest.alignment; for (i, operand) in operands.iter().enumerate() { @@ -143,11 +145,7 @@ impl<'a, 'tcx> MirContext<'a, 'tcx> { // Note: perhaps this should be StructGep, but // note that in some cases the values here will // not be structs but arrays. - let i = if let Some(ref t) = translation { - t[i] as usize - } else { - i - }; + let i = get_memory_index(i); let dest = bcx.gepi(dest.llval, &[0, i]); self.store_operand(&bcx, dest, alignment.to_align(), op); } diff --git a/src/librustc_trans/type_of.rs b/src/librustc_trans/type_of.rs index d4ab6b0782855..9f9126ba83a8f 100644 --- a/src/librustc_trans/type_of.rs +++ b/src/librustc_trans/type_of.rs @@ -214,6 +214,16 @@ impl<'a, 'tcx> CrateContext<'a, 'tcx> { pub fn size_of(&self, ty: Ty<'tcx>) -> machine::llsize { self.layout_of(ty).size(self).bytes() as machine::llsize } + + pub fn over_align_of(&self, t: Ty<'tcx>) + -> Option { + let layout = self.layout_of(t); + if let Some(align) = layout.over_align(&self.tcx().data_layout) { + Some(align as machine::llalign) + } else { + None + } + } } fn llvm_type_name<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, ty: Ty<'tcx>) -> String { diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index 902cfb889f8c2..839af0fa6706c 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -953,6 +953,12 @@ fn check_struct<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, if def.repr.simd() { check_simd(tcx, span, def_id); } + + // if struct is packed and not aligned, check fields for alignment. + // Checks for combining packed and align attrs on single struct are done elsewhere. + if tcx.lookup_adt_def(def_id).repr.packed() && tcx.lookup_adt_def(def_id).repr.align == 0 { + check_packed(tcx, span, def_id); + } } fn check_union<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, @@ -1371,6 +1377,47 @@ pub fn check_simd<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, sp: Span, def_id: DefId } } +fn check_packed<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, sp: Span, def_id: DefId) { + if check_packed_inner(tcx, def_id, &mut Vec::new()) { + struct_span_err!(tcx.sess, sp, E0588, + "packed struct cannot transitively contain a `[repr(align)]` struct").emit(); + } +} + +fn check_packed_inner<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, + def_id: DefId, + stack: &mut Vec) -> bool { + let t = tcx.item_type(def_id); + if stack.contains(&def_id) { + debug!("check_packed_inner: {:?} is recursive", t); + return false; + } + match t.sty { + ty::TyAdt(def, substs) if def.is_struct() => { + if tcx.lookup_adt_def(def.did).repr.align > 0 { + return true; + } + // push struct def_id before checking fields + stack.push(def_id); + for field in &def.struct_variant().fields { + let f = field.ty(tcx, substs); + match f.sty { + ty::TyAdt(def, _) => { + if check_packed_inner(tcx, def.did, stack) { + return true; + } + } + _ => () + } + } + // only need to pop if not early out + stack.pop(); + } + _ => () + } + false +} + #[allow(trivial_numeric_casts)] pub fn check_enum<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, sp: Span, diff --git a/src/librustc_typeck/diagnostics.rs b/src/librustc_typeck/diagnostics.rs index 68afcae2b6746..c50156fa5f271 100644 --- a/src/librustc_typeck/diagnostics.rs +++ b/src/librustc_typeck/diagnostics.rs @@ -4168,5 +4168,6 @@ register_diagnostics! { // but `{}` was found in the type `{}` E0567, // auto traits can not have type parameters E0568, // auto-traits can not have predicates, + E0588, // packed struct cannot transitively contain a `[repr(align)]` struct E0592, // duplicate definitions with name `{}` } diff --git a/src/libsyntax/attr.rs b/src/libsyntax/attr.rs index 6f5f52ff1e953..442345ceb3c18 100644 --- a/src/libsyntax/attr.rs +++ b/src/libsyntax/attr.rs @@ -147,6 +147,24 @@ impl NestedMetaItem { self.meta_item().and_then(|meta_item| meta_item.value_str()) } + /// Returns a name and single literal value tuple of the MetaItem. + pub fn name_value_literal(&self) -> Option<(Name, &Lit)> { + self.meta_item().and_then( + |meta_item| meta_item.meta_item_list().and_then( + |meta_item_list| { + if meta_item_list.len() == 1 { + let nested_item = &meta_item_list[0]; + if nested_item.is_literal() { + Some((meta_item.name(), nested_item.literal().unwrap())) + } else { + None + } + } + else { + None + }})) + } + /// Returns a MetaItem if self is a MetaItem with Kind Word. pub fn word(&self) -> Option<&MetaItem> { self.meta_item().and_then(|meta_item| if meta_item.is_word() { @@ -931,6 +949,7 @@ pub fn find_repr_attrs(diagnostic: &Handler, attr: &Attribute) -> Vec continue } + let mut recognised = false; if let Some(mi) = item.word() { let word = &*mi.name().as_str(); let hint = match word { @@ -941,20 +960,38 @@ pub fn find_repr_attrs(diagnostic: &Handler, attr: &Attribute) -> Vec _ => match int_type_of_word(word) { Some(ity) => Some(ReprInt(ity)), None => { - // Not a word we recognize - span_err!(diagnostic, item.span, E0552, - "unrecognized representation hint"); None } } }; if let Some(h) = hint { + recognised = true; acc.push(h); } - } else { - span_err!(diagnostic, item.span, E0553, - "unrecognized enum representation hint"); + } else if let Some((name, value)) = item.name_value_literal() { + if name == "align" { + recognised = true; + let mut valid_align = false; + if let ast::LitKind::Int(align, ast::LitIntType::Unsuffixed) = value.node { + if align.is_power_of_two() { + // rustc::ty::layout::Align restricts align to <= 32768 + if align <= 32768 { + acc.push(ReprAlign(align as u16)); + valid_align = true; + } + } + } + if !valid_align { + span_err!(diagnostic, item.span, E0589, + "align representation must be a u16 power of two"); + } + } + } + if !recognised { + // Not a word we recognize + span_err!(diagnostic, item.span, E0552, + "unrecognized representation hint"); } } } @@ -986,6 +1023,7 @@ pub enum ReprAttr { ReprExtern, ReprPacked, ReprSimd, + ReprAlign(u16), } #[derive(Eq, Hash, PartialEq, Debug, RustcEncodable, RustcDecodable, Copy, Clone)] diff --git a/src/libsyntax/diagnostic_list.rs b/src/libsyntax/diagnostic_list.rs index 2d59051ec4a53..775775875a4d1 100644 --- a/src/libsyntax/diagnostic_list.rs +++ b/src/libsyntax/diagnostic_list.rs @@ -287,10 +287,10 @@ register_diagnostics! { E0550, // multiple deprecated attributes E0551, // incorrect meta item E0552, // unrecognized representation hint - E0553, // unrecognized enum representation hint E0554, // #[feature] may not be used on the [] release channel E0555, // malformed feature attribute, expected #![feature(...)] E0556, // malformed feature, expected just one word E0557, // feature has been removed E0584, // file for module `..` found at both .. and .. + E0589, // align representation must be a u16 power of two } diff --git a/src/libsyntax_ext/deriving/generic/mod.rs b/src/libsyntax_ext/deriving/generic/mod.rs index 1ff0fec1c96a6..e96883c26f33a 100644 --- a/src/libsyntax_ext/deriving/generic/mod.rs +++ b/src/libsyntax_ext/deriving/generic/mod.rs @@ -773,7 +773,7 @@ fn find_repr_type_name(diagnostic: &Handler, type_attrs: &[ast::Attribute]) -> & for a in type_attrs { for r in &attr::find_repr_attrs(diagnostic, a) { repr_type_name = match *r { - attr::ReprPacked | attr::ReprSimd => continue, + attr::ReprPacked | attr::ReprSimd | attr::ReprAlign(_) => continue, attr::ReprExtern => "i32", attr::ReprInt(attr::SignedInt(ast::IntTy::Is)) => "isize", diff --git a/src/test/compile-fail/attr-usage-repr.rs b/src/test/compile-fail/attr-usage-repr.rs index b07d3e2f90675..c0bfd3690c859 100644 --- a/src/test/compile-fail/attr-usage-repr.rs +++ b/src/test/compile-fail/attr-usage-repr.rs @@ -9,6 +9,7 @@ // except according to those terms. #![allow(dead_code)] +#![feature(attr_literals)] #![feature(repr_simd)] #[repr(C)] //~ ERROR: attribute should be applied to struct, enum or union @@ -29,6 +30,9 @@ struct SInt(f64, f64); #[repr(C)] enum EExtern { A, B } +#[repr(align(8))] //~ ERROR: attribute should be applied to struct +enum EAlign { A, B } + #[repr(packed)] //~ ERROR: attribute should be applied to struct enum EPacked { A, B } diff --git a/src/test/compile-fail/conflicting-repr-hints.rs b/src/test/compile-fail/conflicting-repr-hints.rs index 9e0c0d845ca24..af38673d3650e 100644 --- a/src/test/compile-fail/conflicting-repr-hints.rs +++ b/src/test/compile-fail/conflicting-repr-hints.rs @@ -8,8 +8,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![feature(rustc_attrs)] #![allow(dead_code)] +#![feature(attr_literals)] #[repr(C)] enum A { A } @@ -26,5 +26,7 @@ enum D { D } #[repr(C, packed)] struct E(i32); -#[rustc_error] -fn main() {} //~ ERROR compilation successful +#[repr(packed, align(8))] //~ ERROR conflicting packed and align representation hints +struct F(i32); + +fn main() {} diff --git a/src/test/compile-fail/repr-align.rs b/src/test/compile-fail/repr-align.rs new file mode 100644 index 0000000000000..96dfe1c05f9a7 --- /dev/null +++ b/src/test/compile-fail/repr-align.rs @@ -0,0 +1,22 @@ +// Copyright 2017 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. +#![allow(dead_code)] +#![feature(attr_literals)] + +#[repr(align(16.0))] //~ ERROR: align representation must be a u16 power of two +struct A(i32); + +#[repr(align(15))] //~ ERROR: align representation must be a u16 power of two +struct B(i32); + +#[repr(align(65536))] //~ ERROR: align representation must be a u16 power of tw +struct C(i32); + +fn main() {} diff --git a/src/test/compile-fail/repr-packed-contains-align.rs b/src/test/compile-fail/repr-packed-contains-align.rs new file mode 100644 index 0000000000000..11829dfae8cbe --- /dev/null +++ b/src/test/compile-fail/repr-packed-contains-align.rs @@ -0,0 +1,24 @@ +// Copyright 2017 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(attr_literals)] +#![allow(dead_code)] + +#[repr(align(16))] +struct A(i32); + +struct B(A); + +#[repr(packed)] +struct C(A); //~ ERROR: packed struct cannot transitively contain a `[repr(align)]` struct + +#[repr(packed)] +struct D(B); //~ ERROR: packed struct cannot transitively contain a `[repr(align)]` struct + +fn main() {} diff --git a/src/test/run-pass/align-struct.rs b/src/test/run-pass/align-struct.rs new file mode 100644 index 0000000000000..4793189d366df --- /dev/null +++ b/src/test/run-pass/align-struct.rs @@ -0,0 +1,195 @@ +// Copyright 2017 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(attr_literals)] + +use std::mem; + +// Raising alignment +#[repr(align(16))] +struct Align16(i32); + +// Lowering has no effect +#[repr(align(1))] +struct Align1(i32); + +// Multiple attributes take the max +#[repr(align(4))] +#[repr(align(16))] +#[repr(align(8))] +struct AlignMany(i32); + +// Raising alignment may not alter size. +#[repr(align(8))] +#[allow(dead_code)] +struct Align8Many { + a: i32, + b: i32, + c: i32, + d: u8, +} + +enum Enum { + #[allow(dead_code)] + A(i32), + B(Align16) +} + +// Nested alignment - use `#[repr(C)]` to suppress field reordering for sizeof test +#[repr(C)] +struct Nested { + a: i32, + b: i32, + c: Align16, + d: i8, +} + +#[repr(packed)] +struct Packed(i32); + +#[repr(align(16))] +struct AlignContainsPacked { + a: Packed, + b: Packed, +} + +impl Align16 { + // return aligned type + pub fn new(i: i32) -> Align16 { + Align16(i) + } + // pass aligned type + pub fn consume(a: Align16) -> i32 { + a.0 + } +} + +const CONST_ALIGN16: Align16 = Align16(7); +static STATIC_ALIGN16: Align16 = Align16(8); + +// Check the actual address is aligned +fn is_aligned_to(p: &T, align: usize) -> bool { + let addr = p as *const T as usize; + (addr & (align - 1)) == 0 +} + +pub fn main() { + // check alignment and size by type and value + assert_eq!(mem::align_of::(), 16); + assert_eq!(mem::size_of::(), 16); + + let a = Align16(7); + assert_eq!(a.0, 7); + assert_eq!(mem::align_of_val(&a), 16); + assert_eq!(mem::size_of_val(&a), 16); + + assert!(is_aligned_to(&a, 16)); + + // lowering should have no effect + assert_eq!(mem::align_of::(), 4); + assert_eq!(mem::size_of::(), 4); + let a = Align1(7); + assert_eq!(a.0, 7); + assert_eq!(mem::align_of_val(&a), 4); + assert_eq!(mem::size_of_val(&a), 4); + assert!(is_aligned_to(&a, 4)); + + // when multiple attributes are specified the max should be used + assert_eq!(mem::align_of::(), 16); + assert_eq!(mem::size_of::(), 16); + let a = AlignMany(7); + assert_eq!(a.0, 7); + assert_eq!(mem::align_of_val(&a), 16); + assert_eq!(mem::size_of_val(&a), 16); + assert!(is_aligned_to(&a, 16)); + + // raising alignment should not reduce size + assert_eq!(mem::align_of::(), 8); + assert_eq!(mem::size_of::(), 16); + let a = Align8Many { a: 1, b: 2, c: 3, d: 4 }; + assert_eq!(a.a, 1); + assert_eq!(mem::align_of_val(&a), 8); + assert_eq!(mem::size_of_val(&a), 16); + assert!(is_aligned_to(&a, 8)); + + // return type + let a = Align16::new(1); + assert_eq!(mem::align_of_val(&a), 16); + assert_eq!(mem::size_of_val(&a), 16); + assert_eq!(a.0, 1); + assert!(is_aligned_to(&a, 16)); + assert_eq!(Align16::consume(a), 1); + + // check const alignment, size and value + assert_eq!(mem::align_of_val(&CONST_ALIGN16), 16); + assert_eq!(mem::size_of_val(&CONST_ALIGN16), 16); + assert_eq!(CONST_ALIGN16.0, 7); + assert!(is_aligned_to(&CONST_ALIGN16, 16)); + + // check global static alignment, size and value + assert_eq!(mem::align_of_val(&STATIC_ALIGN16), 16); + assert_eq!(mem::size_of_val(&STATIC_ALIGN16), 16); + assert_eq!(STATIC_ALIGN16.0, 8); + assert!(is_aligned_to(&STATIC_ALIGN16, 16)); + + // Note that the size of Nested may change if struct field re-ordering is enabled + assert_eq!(mem::align_of::(), 16); + assert_eq!(mem::size_of::(), 48); + let a = Nested{ a: 1, b: 2, c: Align16(3), d: 4}; + assert_eq!(mem::align_of_val(&a), 16); + assert_eq!(mem::align_of_val(&a.b), 4); + assert_eq!(mem::align_of_val(&a.c), 16); + assert_eq!(mem::size_of_val(&a), 48); + assert!(is_aligned_to(&a, 16)); + // check the correct fields are indexed + assert_eq!(a.a, 1); + assert_eq!(a.b, 2); + assert_eq!(a.c.0, 3); + assert_eq!(a.d, 4); + + // enum should be aligned to max alignment + assert_eq!(mem::align_of::(), 16); + assert_eq!(mem::align_of_val(&Enum::B(Align16(0))), 16); + let e = Enum::B(Align16(15)); + match e { + Enum::B(ref a) => { + assert_eq!(a.0, 15); + assert_eq!(mem::align_of_val(a), 16); + assert_eq!(mem::size_of_val(a), 16); + }, + _ => () + } + assert!(is_aligned_to(&e, 16)); + + // arrays of aligned elements should also be aligned + assert_eq!(mem::align_of::<[Align16;2]>(), 16); + assert_eq!(mem::size_of::<[Align16;2]>(), 32); + + let a = [Align16(0), Align16(1)]; + assert_eq!(mem::align_of_val(&a[0]), 16); + assert_eq!(mem::align_of_val(&a[1]), 16); + assert!(is_aligned_to(&a, 16)); + + // check heap value is aligned + assert_eq!(mem::align_of_val(Box::new(Align16(0)).as_ref()), 16); + + // check heap array is aligned + let a = vec!(Align16(0), Align16(1)); + assert_eq!(mem::align_of_val(&a[0]), 16); + assert_eq!(mem::align_of_val(&a[1]), 16); + + assert_eq!(mem::align_of::(), 16); + assert_eq!(mem::size_of::(), 16); + let a = AlignContainsPacked { a: Packed(1), b: Packed(2) }; + assert_eq!(mem::align_of_val(&a), 16); + assert_eq!(mem::align_of_val(&a.a), 1); + assert_eq!(mem::align_of_val(&a.b), 1); + assert_eq!(mem::size_of_val(&a), 16); + assert!(is_aligned_to(&a, 16)); +} diff --git a/src/test/ui/print_type_sizes/repr-align.rs b/src/test/ui/print_type_sizes/repr-align.rs new file mode 100644 index 0000000000000..ea2910f86c458 --- /dev/null +++ b/src/test/ui/print_type_sizes/repr-align.rs @@ -0,0 +1,42 @@ +// 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. + +// compile-flags: -Z print-type-sizes + +// This file illustrates how padding is handled: alignment +// requirements can lead to the introduction of padding, either before +// fields or at the end of the structure as a whole. +// +// It avoids using u64/i64 because on some targets that is only 4-byte +// aligned (while on most it is 8-byte aligned) and so the resulting +// padding and overall computed sizes can be quite different. +#![feature(attr_literals)] +#![allow(dead_code)] + +#[repr(align(16))] +#[derive(Default)] +struct A(i32); + +enum E { + A(i32), + B(A) +} + +#[derive(Default)] +struct S { + a: i32, + b: i32, + c: A, + d: i8, +} + +fn main() { + let _s: S = Default::default(); +} diff --git a/src/test/ui/print_type_sizes/repr-align.stdout b/src/test/ui/print_type_sizes/repr-align.stdout new file mode 100644 index 0000000000000..7df12f040b15d --- /dev/null +++ b/src/test/ui/print_type_sizes/repr-align.stdout @@ -0,0 +1,16 @@ +print-type-size type: `E`: 32 bytes, alignment: 16 bytes +print-type-size discriminant: 4 bytes +print-type-size variant `A`: 4 bytes +print-type-size field `.0`: 4 bytes +print-type-size variant `B`: 28 bytes +print-type-size padding: 12 bytes +print-type-size field `.0`: 16 bytes, alignment: 16 bytes +print-type-size type: `S`: 32 bytes, alignment: 16 bytes +print-type-size field `.c`: 16 bytes +print-type-size field `.a`: 4 bytes +print-type-size field `.b`: 4 bytes +print-type-size field `.d`: 1 bytes +print-type-size end padding: 7 bytes +print-type-size type: `A`: 16 bytes, alignment: 16 bytes +print-type-size field `.0`: 4 bytes +print-type-size end padding: 12 bytes From 7971a47eff4b178ffe7c38c67b1926dacea4c0dd Mon Sep 17 00:00:00 2001 From: Cameron Hart Date: Sat, 25 Mar 2017 19:00:49 +1100 Subject: [PATCH 2/4] Added feature gate, updated error messages and tests. --- src/doc/unstable-book/src/SUMMARY.md | 1 + .../src/language-features/repr-align.md | 11 +++++++++++ src/libsyntax/attr.rs | 13 +++++++++---- src/libsyntax/diagnostic_list.rs | 2 +- src/libsyntax/feature_gate.rs | 8 ++++++++ src/test/compile-fail/conflicting-repr-hints.rs | 1 + src/test/compile-fail/feature-gate-repr_align.rs | 15 +++++++++++++++ src/test/compile-fail/repr-align.rs | 7 ++++--- .../compile-fail/repr-packed-contains-align.rs | 1 + src/test/run-pass/align-struct.rs | 1 + src/test/ui/print_type_sizes/repr-align.rs | 1 + 11 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 src/doc/unstable-book/src/language-features/repr-align.md create mode 100644 src/test/compile-fail/feature-gate-repr_align.rs diff --git a/src/doc/unstable-book/src/SUMMARY.md b/src/doc/unstable-book/src/SUMMARY.md index 3e0415439774c..ee4e568a5ca9a 100644 --- a/src/doc/unstable-book/src/SUMMARY.md +++ b/src/doc/unstable-book/src/SUMMARY.md @@ -72,6 +72,7 @@ - [proc_macro](language-features/proc-macro.md) - [quote](language-features/quote.md) - [relaxed_adts](language-features/relaxed-adts.md) + - [repr_align](language-features/repr-align.md) - [repr_simd](language-features/repr-simd.md) - [rustc_attrs](language-features/rustc-attrs.md) - [rustc_diagnostic_macros](language-features/rustc-diagnostic-macros.md) diff --git a/src/doc/unstable-book/src/language-features/repr-align.md b/src/doc/unstable-book/src/language-features/repr-align.md new file mode 100644 index 0000000000000..deea04f4c51cc --- /dev/null +++ b/src/doc/unstable-book/src/language-features/repr-align.md @@ -0,0 +1,11 @@ +# `repr_align` + +The tracking issue for this feature is: [#33626] + +[#33626]: https://github.com/rust-lang/rust/issues/33626 + +------------------------ + + + + diff --git a/src/libsyntax/attr.rs b/src/libsyntax/attr.rs index 442345ceb3c18..82492d976276e 100644 --- a/src/libsyntax/attr.rs +++ b/src/libsyntax/attr.rs @@ -972,19 +972,24 @@ pub fn find_repr_attrs(diagnostic: &Handler, attr: &Attribute) -> Vec } else if let Some((name, value)) = item.name_value_literal() { if name == "align" { recognised = true; - let mut valid_align = false; + let mut align_error = None; if let ast::LitKind::Int(align, ast::LitIntType::Unsuffixed) = value.node { if align.is_power_of_two() { // rustc::ty::layout::Align restricts align to <= 32768 if align <= 32768 { acc.push(ReprAlign(align as u16)); - valid_align = true; + } else { + align_error = Some("larger than 32768"); } + } else { + align_error = Some("not a power of two"); } + } else { + align_error = Some("not an unsuffixed integer"); } - if !valid_align { + if let Some(align_error) = align_error { span_err!(diagnostic, item.span, E0589, - "align representation must be a u16 power of two"); + "invalid `repr(align)` attribute: {}", align_error); } } } diff --git a/src/libsyntax/diagnostic_list.rs b/src/libsyntax/diagnostic_list.rs index 775775875a4d1..01d1277ea6265 100644 --- a/src/libsyntax/diagnostic_list.rs +++ b/src/libsyntax/diagnostic_list.rs @@ -292,5 +292,5 @@ register_diagnostics! { E0556, // malformed feature, expected just one word E0557, // feature has been removed E0584, // file for module `..` found at both .. and .. - E0589, // align representation must be a u16 power of two + E0589, // invalid `repr(align)` attribute } diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 175447e111270..9b55a860b3595 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -338,6 +338,9 @@ declare_features! ( // Allows the `catch {...}` expression (active, catch_expr, "1.17.0", Some(31436)), + // Allows `repr(align(u16))` struct attribute (RFC 1358) + (active, repr_align, "1.17.0", Some(33626)), + // See rust-lang/rfcs#1414. Allows code like `let x: &'static u32 = &42` to work. (active, rvalue_static_promotion, "1.15.1", Some(38865)), @@ -1189,6 +1192,11 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { and possibly buggy"); } + if item.check_name("align") { + gate_feature_post!(&self, repr_align, i.span, + "the struct `#[repr(align(u16))]` attribute \ + is experimental"); + } } } } diff --git a/src/test/compile-fail/conflicting-repr-hints.rs b/src/test/compile-fail/conflicting-repr-hints.rs index af38673d3650e..01fa3ffbaa6ae 100644 --- a/src/test/compile-fail/conflicting-repr-hints.rs +++ b/src/test/compile-fail/conflicting-repr-hints.rs @@ -10,6 +10,7 @@ #![allow(dead_code)] #![feature(attr_literals)] +#![feature(repr_align)] #[repr(C)] enum A { A } diff --git a/src/test/compile-fail/feature-gate-repr_align.rs b/src/test/compile-fail/feature-gate-repr_align.rs new file mode 100644 index 0000000000000..8e986e197f269 --- /dev/null +++ b/src/test/compile-fail/feature-gate-repr_align.rs @@ -0,0 +1,15 @@ +// Copyright 2017 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(attr_literals)] + +#[repr(align(64))] +struct Foo(u64, u64); //~ error: the struct `#[repr(align(u16))]` attribute is experimental + +fn main() {} diff --git a/src/test/compile-fail/repr-align.rs b/src/test/compile-fail/repr-align.rs index 96dfe1c05f9a7..eb0b27fe9c07e 100644 --- a/src/test/compile-fail/repr-align.rs +++ b/src/test/compile-fail/repr-align.rs @@ -9,14 +9,15 @@ // except according to those terms. #![allow(dead_code)] #![feature(attr_literals)] +#![feature(repr_align)] -#[repr(align(16.0))] //~ ERROR: align representation must be a u16 power of two +#[repr(align(16.0))] //~ ERROR: invalid `repr(align)` attribute: not an unsuffixed integer struct A(i32); -#[repr(align(15))] //~ ERROR: align representation must be a u16 power of two +#[repr(align(15))] //~ ERROR: invalid `repr(align)` attribute: not a power of two struct B(i32); -#[repr(align(65536))] //~ ERROR: align representation must be a u16 power of tw +#[repr(align(65536))] //~ ERROR: invalid `repr(align)` attribute: larger than 32768 struct C(i32); fn main() {} diff --git a/src/test/compile-fail/repr-packed-contains-align.rs b/src/test/compile-fail/repr-packed-contains-align.rs index 11829dfae8cbe..c584dcf3e5993 100644 --- a/src/test/compile-fail/repr-packed-contains-align.rs +++ b/src/test/compile-fail/repr-packed-contains-align.rs @@ -8,6 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. #![feature(attr_literals)] +#![feature(repr_align)] #![allow(dead_code)] #[repr(align(16))] diff --git a/src/test/run-pass/align-struct.rs b/src/test/run-pass/align-struct.rs index 4793189d366df..0b9a3594502b0 100644 --- a/src/test/run-pass/align-struct.rs +++ b/src/test/run-pass/align-struct.rs @@ -8,6 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. #![feature(attr_literals)] +#![feature(repr_align)] use std::mem; diff --git a/src/test/ui/print_type_sizes/repr-align.rs b/src/test/ui/print_type_sizes/repr-align.rs index ea2910f86c458..e9b43145de469 100644 --- a/src/test/ui/print_type_sizes/repr-align.rs +++ b/src/test/ui/print_type_sizes/repr-align.rs @@ -18,6 +18,7 @@ // aligned (while on most it is 8-byte aligned) and so the resulting // padding and overall computed sizes can be quite different. #![feature(attr_literals)] +#![feature(repr_align)] #![allow(dead_code)] #[repr(align(16))] From c219cdfa11b0bb23f1dabbef9415ffb94f10386a Mon Sep 17 00:00:00 2001 From: Cameron Hart Date: Mon, 10 Apr 2017 21:40:24 +1200 Subject: [PATCH 3/4] Removed sizing parameter from struct_llfields. --- src/librustc_trans/adt.rs | 26 +++++++++----------------- src/librustc_trans/mir/lvalue.rs | 2 +- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/librustc_trans/adt.rs b/src/librustc_trans/adt.rs index 3c73aba7cd7ed..5326e9344c89a 100644 --- a/src/librustc_trans/adt.rs +++ b/src/librustc_trans/adt.rs @@ -90,12 +90,12 @@ pub fn compute_fields<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, t: Ty<'tcx>, /// and fill in the actual contents in a second pass to prevent /// unbounded recursion; see also the comments in `trans::type_of`. pub fn type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, t: Ty<'tcx>) -> Type { - generic_type_of(cx, t, None, false) + generic_type_of(cx, t, None) } pub fn incomplete_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, t: Ty<'tcx>, name: &str) -> Type { - generic_type_of(cx, t, Some(name), false) + generic_type_of(cx, t, Some(name)) } pub fn finish_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, @@ -114,7 +114,7 @@ pub fn finish_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, _ => unreachable!() }; let fields = compute_fields(cx, t, nonnull_variant_index as usize, true); - llty.set_struct_body(&struct_llfields(cx, &fields, nonnull_variant, false), + llty.set_struct_body(&struct_llfields(cx, &fields, nonnull_variant), packed) }, _ => bug!("This function cannot handle {} with layout {:#?}", t, l) @@ -123,10 +123,9 @@ pub fn finish_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, fn generic_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, t: Ty<'tcx>, - name: Option<&str>, - sizing: bool) -> Type { + name: Option<&str>) -> Type { let l = cx.layout_of(t); - debug!("adt::generic_type_of t: {:?} name: {:?} sizing: {}", t, name, sizing); + debug!("adt::generic_type_of t: {:?} name: {:?}", t, name); match *l { layout::CEnum { discr, .. } => Type::from_integer(cx, discr), layout::RawNullablePointer { nndiscr, .. } => { @@ -146,11 +145,10 @@ fn generic_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, let fields = compute_fields(cx, t, nndiscr as usize, false); match name { None => { - Type::struct_(cx, &struct_llfields(cx, &fields, nonnull, sizing), + Type::struct_(cx, &struct_llfields(cx, &fields, nonnull), nonnull.packed) } Some(name) => { - assert_eq!(sizing, false); Type::named_struct(cx, name) } } @@ -161,13 +159,12 @@ fn generic_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, let fields = compute_fields(cx, t, 0, true); match name { None => { - let fields = struct_llfields(cx, &fields, &variant, sizing); + let fields = struct_llfields(cx, &fields, &variant); Type::struct_(cx, &fields, variant.packed) } Some(name) => { // Hypothesis: named_struct's can never need a // drop flag. (... needs validation.) - assert_eq!(sizing, false); Type::named_struct(cx, name) } } @@ -256,19 +253,14 @@ pub fn struct_llfields_index(variant: &layout::Struct, index: usize) -> usize { pub fn struct_llfields<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, field_tys: &Vec>, - variant: &layout::Struct, - sizing: bool) -> Vec { - if sizing { - bug!(); - } + variant: &layout::Struct) -> Vec { debug!("struct_llfields: variant: {:?}", variant); let mut first_field = true; let mut min_offset = 0; let mut result: Vec = Vec::with_capacity(field_tys.len() * 2); let field_iter = variant.field_index_by_increasing_offset().map(|i| { (i, field_tys[i as usize], variant.offsets[i as usize].bytes()) }); - for (index, ty, target_offset) in field_iter.filter( - |&(_, ty, _)| !sizing || cx.shared().type_is_sized(ty)) { + for (index, ty, target_offset) in field_iter { if first_field { debug!("struct_llfields: {} ty: {} min_offset: {} target_offset: {}", index, ty, min_offset, target_offset); diff --git a/src/librustc_trans/mir/lvalue.rs b/src/librustc_trans/mir/lvalue.rs index 9a461e4aafc23..88e46b5c99a44 100644 --- a/src/librustc_trans/mir/lvalue.rs +++ b/src/librustc_trans/mir/lvalue.rs @@ -132,7 +132,7 @@ impl<'a, 'tcx> LvalueRef<'tcx> { let alignment = self.alignment | Alignment::from_packed(st.packed); - let llfields = adt::struct_llfields(ccx, fields, st, false); + let llfields = adt::struct_llfields(ccx, fields, st); let ptr_val = if needs_cast { let real_ty = Type::struct_(ccx, &llfields[..], st.packed); bcx.pointercast(self.llval, real_ty.ptr_to()) From 946f8e6a59242a8112c6983d1336fef54bc55b9a Mon Sep 17 00:00:00 2001 From: Cameron Hart Date: Sat, 22 Apr 2017 09:38:23 +1000 Subject: [PATCH 4/4] Use primitive align for tagged enum fill. Hopefully will fix assert on ARM where vector types are being used as the fill type for enums containing repr aligned types greater than the largest possible native type, thus don't match the Layout's alignment and triggers an assert. --- src/librustc_trans/adt.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/librustc_trans/adt.rs b/src/librustc_trans/adt.rs index 5326e9344c89a..d1c1dd7436a5b 100644 --- a/src/librustc_trans/adt.rs +++ b/src/librustc_trans/adt.rs @@ -185,7 +185,7 @@ fn generic_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, } } } - layout::General { discr, size, align, .. } => { + layout::General { discr, size, align, primitive_align, .. } => { // We need a representation that has: // * The alignment of the most-aligned field // * The size of the largest variant (rounded up to that alignment) @@ -198,14 +198,15 @@ fn generic_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>, // of the size. let size = size.bytes(); let align = align.abi(); + let primitive_align = primitive_align.abi(); assert!(align <= std::u32::MAX as u64); let discr_ty = Type::from_integer(cx, discr); let discr_size = discr.size().bytes(); let padded_discr_size = roundup(discr_size, align as u32); let variant_part_size = size-padded_discr_size; - let variant_fill = union_fill(cx, variant_part_size, align); + let variant_fill = union_fill(cx, variant_part_size, primitive_align); - assert_eq!(machine::llalign_of_min(cx, variant_fill), align as u32); + assert_eq!(machine::llalign_of_min(cx, variant_fill), primitive_align as u32); assert_eq!(padded_discr_size % discr_size, 0); // Ensure discr_ty can fill pad evenly let fields: Vec = [discr_ty,