From 2be697bc215f19b4bf17df6b9b56626ab7b1d994 Mon Sep 17 00:00:00 2001 From: Robin Kruppe Date: Wed, 3 Jan 2018 17:43:30 +0100 Subject: [PATCH] Implement repr(transparent) --- .../src/language-features/repr-transparent.md | 176 +++++++++++++++++ src/librustc/diagnostics.rs | 64 ++++++- src/librustc/hir/check_attr.rs | 25 ++- src/librustc/lib.rs | 1 + src/librustc/ty/mod.rs | 6 +- src/librustc_lint/types.rs | 17 +- src/librustc_typeck/check/mod.rs | 38 ++++ src/librustc_typeck/diagnostics.rs | 93 +++++++++ src/librustc_typeck/lib.rs | 1 + src/libsyntax/attr.rs | 5 +- src/libsyntax/feature_gate.rs | 8 + src/libsyntax_ext/deriving/generic/mod.rs | 4 +- .../codegen/repr-transparent-aggregates-1.rs | 53 ++++++ .../codegen/repr-transparent-aggregates-2.rs | 54 ++++++ src/test/codegen/repr-transparent.rs | 177 ++++++++++++++++++ src/test/compile-fail/lint-ctypes.rs | 28 ++- .../repr-transparent-other-items.rs | 40 ++++ .../repr-transparent-other-reprs.rs | 28 +++ src/test/compile-fail/repr-transparent.rs | 51 +++++ src/test/ui/feature-gate-repr_transparent.rs | 14 ++ .../ui/feature-gate-repr_transparent.stderr | 10 + 21 files changed, 881 insertions(+), 12 deletions(-) create mode 100644 src/doc/unstable-book/src/language-features/repr-transparent.md create mode 100644 src/test/codegen/repr-transparent-aggregates-1.rs create mode 100644 src/test/codegen/repr-transparent-aggregates-2.rs create mode 100644 src/test/codegen/repr-transparent.rs create mode 100644 src/test/compile-fail/repr-transparent-other-items.rs create mode 100644 src/test/compile-fail/repr-transparent-other-reprs.rs create mode 100644 src/test/compile-fail/repr-transparent.rs create mode 100644 src/test/ui/feature-gate-repr_transparent.rs create mode 100644 src/test/ui/feature-gate-repr_transparent.stderr diff --git a/src/doc/unstable-book/src/language-features/repr-transparent.md b/src/doc/unstable-book/src/language-features/repr-transparent.md new file mode 100644 index 0000000000000..62202dc96fd46 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/repr-transparent.md @@ -0,0 +1,176 @@ +# `repr_transparent` + +The tracking issue for this feature is: [#43036] + +[#43036]: https://github.com/rust-lang/rust/issues/43036 + +------------------------ + +This feature enables the `repr(transparent)` attribute on structs, which enables +the use of newtypes without the usual ABI implications of wrapping the value in +a struct. + +## Background + +It's sometimes useful to add additional type safety by introducing *newtypes*. +For example, code that handles numeric quantities in different units such as +millimeters, centimeters, grams, kilograms, etc. may want to use the type system +to rule out mistakes such as adding millimeters to grams: + +```rust +use std::ops::Add; + +struct Millimeters(f64); +struct Grams(f64); + +impl Add for Millimeters { + type Output = Millimeters; + + fn add(self, other: Millimeters) -> Millimeters { + Millimeters(self.0 + other.0) + } +} + +// Likewise: impl Add for Grams {} +``` + +Other uses of newtypes include using `PhantomData` to add lifetimes to raw +pointers or to implement the "phantom types" pattern. See the [PhantomData] +documentation and [the Nomicon][nomicon-phantom] for more details. + +The added type safety is especially useful when interacting with C or other +languages. However, in those cases we need to ensure the newtypes we add do not +introduce incompatibilities with the C ABI. + +## Newtypes in FFI + +Luckily, `repr(C)` newtypes are laid out just like the type they wrap on all +platforms which Rust currently supports, and likely on many more. For example, +consider this C declaration: + +```C +struct Object { + double weight; //< in grams + double height; //< in millimeters + // ... +} + +void frobnicate(struct Object *); +``` + +While using this C code from Rust, we could add `repr(C)` to the `Grams` and +`Millimeters` newtypes introduced above and use them to add some type safety +while staying compatible with the memory layout of `Object`: + +```rust,no_run +#[repr(C)] +struct Grams(f64); + +#[repr(C)] +struct Millimeters(f64); + +#[repr(C)] +struct Object { + weight: Grams, + height: Millimeters, + // ... +} + +extern { + fn frobnicate(_: *mut Object); +} +``` + +This works even when adding some `PhantomData` fields, because they are +zero-sized and therefore don't have to affect the memory layout. + +However, there's more to the ABI than just memory layout: there's also the +question of how function call arguments and return values are passed. Many +common ABI treat a struct containing a single field differently from that field +itself, at least when the field is a scalar (e.g., integer or float or pointer). + +To continue the above example, suppose the C library also exposes a function +like this: + +```C +double calculate_weight(double height); +``` + +Using our newtypes on the Rust side like this will cause an ABI mismatch on many +platforms: + +```rust,ignore +extern { + fn calculate_weight(height: Millimeters) -> Grams; +} +``` + +For example, on x86_64 Linux, Rust will pass the argument in an integer +register, while the C function expects the argument to be in a floating-point +register. Likewise, the C function will return the result in a floating-point +register while Rust will expect it in an integer register. + +Note that this problem is not specific to floats: To give another example, +32-bit x86 linux will pass and return `struct Foo(i32);` on the stack while +`i32` is placed in registers. + +## Enter `repr(transparent)` + +So while `repr(C)` happens to do the right thing with respect to memory layout, +it's not quite the right tool for newtypes in FFI. Instead of declaring a C +struct, we need to communicate to the Rust compiler that our newtype is just for +type safety on the Rust side. This is what `repr(transparent)` does. + +The attribute can be applied to a newtype-like structs that contains a single +field. It indicates that the newtype should be represented exactly like that +field's type, i.e., the newtype should be ignored for ABI purpopses: not only is +it laid out the same in memory, it is also passed identically in function calls. + +In the above example, the ABI mismatches can be prevented by making the newtypes +`Grams` and `Millimeters` transparent like this: + +```rust +#![feature(repr_transparent)] + +#[repr(transparent)] +struct Grams(f64); + +#[repr(transparent)] +struct Millimeters(f64); +``` + +In addition to that single field, any number of zero-sized fields are permitted, +including but not limited to `PhantomData`: + +```rust +#![feature(repr_transparent)] + +use std::marker::PhantomData; + +struct Foo { /* ... */ } + +#[repr(transparent)] +struct FooPtrWithLifetime<'a>(*const Foo, PhantomData<&'a Foo>); + +#[repr(transparent)] +struct NumberWithUnit(T, PhantomData); + +struct CustomZst; + +#[repr(transparent)] +struct PtrWithCustomZst<'a> { + ptr: FooPtrWithLifetime<'a>, + some_marker: CustomZst, +} +``` + +Transparent structs can be nested: `PtrWithCustomZst` is also represented +exactly like `*const Foo`. + +Because `repr(transparent)` delegates all representation concerns to another +type, it is incompatible with all other `repr(..)` attributes. It also cannot be +applied to enums, unions, empty structs, structs whose fields are all +zero-sized, or structs with *multiple* non-zero-sized fields. + +[PhantomData]: https://doc.rust-lang.org/std/marker/struct.PhantomData.html +[nomicon-phantom]: https://doc.rust-lang.org/nomicon/phantom-data.html diff --git a/src/librustc/diagnostics.rs b/src/librustc/diagnostics.rs index 7b48e7801dfb1..1d7cde8126547 100644 --- a/src/librustc/diagnostics.rs +++ b/src/librustc/diagnostics.rs @@ -2003,7 +2003,69 @@ that refers to itself. That is permitting, since the closure would be invoking itself via a virtual call, and hence does not directly reference its own *type*. -"##, } +"##, + +E0692: r##" +A `repr(transparent)` type was also annotated with other, incompatible +representation hints. + +Erroneous code example: + +```compile_fail,E0692 +#![feature(repr_transparent)] + +#[repr(transparent, C)] // error: incompatible representation hints +struct Grams(f32); +``` + +A type annotated as `repr(transparent)` delegates all representation concerns to +another type, so adding more representation hints is contradictory. Remove +either the `transparent` hint or the other hints, like this: + +``` +#![feature(repr_transparent)] + +#[repr(transparent)] +struct Grams(f32); +``` + +Alternatively, move the other attributes to the contained type: + +``` +#![feature(repr_transparent)] + +#[repr(C)] +struct Foo { + x: i32, + // ... +} + +#[repr(transparent)] +struct FooWrapper(Foo); +``` + +Note that introducing another `struct` just to have a place for the other +attributes may have unintended side effects on the representation: + +``` +#![feature(repr_transparent)] + +#[repr(transparent)] +struct Grams(f32); + +#[repr(C)] +struct Float(f32); + +#[repr(transparent)] +struct Grams2(Float); // this is not equivalent to `Grams` above +``` + +Here, `Grams2` is a not equivalent to `Grams` -- the former transparently wraps +a (non-transparent) struct containing a single float, while `Grams` is a +transparent wrapper around a float. This can make a difference for the ABI. +"##, + +} register_diagnostics! { diff --git a/src/librustc/hir/check_attr.rs b/src/librustc/hir/check_attr.rs index 4b528a0fdc778..a073910fdc83f 100644 --- a/src/librustc/hir/check_attr.rs +++ b/src/librustc/hir/check_attr.rs @@ -92,6 +92,7 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> { let mut int_reprs = 0; let mut is_c = false; let mut is_simd = false; + let mut is_transparent = false; for hint in &hints { let name = if let Some(name) = hint.name() { @@ -137,6 +138,14 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> { continue } } + "transparent" => { + is_transparent = true; + if target != Target::Struct { + ("a", "struct") + } else { + continue + } + } "i8" | "u8" | "i16" | "u16" | "i32" | "u32" | "i64" | "u64" | "isize" | "usize" => { @@ -155,14 +164,22 @@ impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> { .emit(); } + // Just point at all repr hints if there are any incompatibilities. + // This is not ideal, but tracking precisely which ones are at fault is a huge hassle. + let hint_spans = hints.iter().map(|hint| hint.span); + + // Error on repr(transparent, ). + if is_transparent && hints.len() > 1 { + let hint_spans: Vec<_> = hint_spans.clone().collect(); + span_err!(self.tcx.sess, hint_spans, E0692, + "transparent struct cannot have other repr hints"); + } // Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8) if (int_reprs > 1) || (is_simd && is_c) || (int_reprs == 1 && is_c && is_c_like_enum(item)) { - // Just point at all repr hints. This is not ideal, but tracking - // precisely which ones are at fault is a huge hassle. - let spans: Vec<_> = hints.iter().map(|hint| hint.span).collect(); - span_warn!(self.tcx.sess, spans, E0566, + let hint_spans: Vec<_> = hint_spans.collect(); + span_warn!(self.tcx.sess, hint_spans, E0566, "conflicting representation hints"); } } diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs index 075ee0b8c7c28..d727dfb0c4b2d 100644 --- a/src/librustc/lib.rs +++ b/src/librustc/lib.rs @@ -44,6 +44,7 @@ #![feature(box_syntax)] #![feature(conservative_impl_trait)] #![feature(const_fn)] +#![feature(copy_closures, clone_closures)] #![feature(core_intrinsics)] #![feature(drain_filter)] #![feature(dyn_trait)] diff --git a/src/librustc/ty/mod.rs b/src/librustc/ty/mod.rs index 2b4d2c80c6f9e..deaea70f760f1 100644 --- a/src/librustc/ty/mod.rs +++ b/src/librustc/ty/mod.rs @@ -1499,8 +1499,9 @@ bitflags! { const IS_C = 1 << 0; const IS_PACKED = 1 << 1; const IS_SIMD = 1 << 2; + const IS_TRANSPARENT = 1 << 3; // Internal only for now. If true, don't reorder fields. - const IS_LINEAR = 1 << 3; + const IS_LINEAR = 1 << 4; // Any of these flags being set prevent field reordering optimisation. const IS_UNOPTIMISABLE = ReprFlags::IS_C.bits | @@ -1540,6 +1541,7 @@ impl ReprOptions { flags.insert(match r { attr::ReprC => ReprFlags::IS_C, attr::ReprPacked => ReprFlags::IS_PACKED, + attr::ReprTransparent => ReprFlags::IS_TRANSPARENT, attr::ReprSimd => ReprFlags::IS_SIMD, attr::ReprInt(i) => { size = Some(i); @@ -1567,6 +1569,8 @@ impl ReprOptions { #[inline] pub fn packed(&self) -> bool { self.flags.contains(ReprFlags::IS_PACKED) } #[inline] + pub fn transparent(&self) -> bool { self.flags.contains(ReprFlags::IS_TRANSPARENT) } + #[inline] pub fn linear(&self) -> bool { self.flags.contains(ReprFlags::IS_LINEAR) } pub fn discr_type(&self) -> attr::IntType { diff --git a/src/librustc_lint/types.rs b/src/librustc_lint/types.rs index bf5d16696f9cd..2267d2b89ccf1 100644 --- a/src/librustc_lint/types.rs +++ b/src/librustc_lint/types.rs @@ -416,7 +416,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { } match def.adt_kind() { AdtKind::Struct => { - if !def.repr.c() { + if !def.repr.c() && !def.repr.transparent() { return FfiUnsafe("found struct without foreign-function-safe \ representation annotation in foreign module, \ consider adding a #[repr(C)] attribute to the type"); @@ -427,13 +427,24 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> { adding a member to this struct"); } - // We can't completely trust repr(C) markings; make sure the - // fields are actually safe. + // We can't completely trust repr(C) and repr(transparent) markings; + // make sure the fields are actually safe. let mut all_phantom = true; for field in &def.non_enum_variant().fields { let field_ty = cx.fully_normalize_associated_types_in( &field.ty(cx, substs) ); + // repr(transparent) types are allowed to have arbitrary ZSTs, not just + // PhantomData -- skip checking all ZST fields + if def.repr.transparent() { + let is_zst = (cx, cx.param_env(field.did)) + .layout_of(field_ty) + .map(|layout| layout.is_zst()) + .unwrap_or(false); + if is_zst { + continue; + } + } let r = self.check_type_for_ffi(cache, field_ty); match r { FfiSafe => { diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index 69236d77ed328..cb8ce8d5ac331 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -101,6 +101,7 @@ use rustc::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc::ty::fold::TypeFoldable; use rustc::ty::maps::Providers; use rustc::ty::util::{Representability, IntTypeExt}; +use rustc::ty::layout::LayoutOf; use errors::{DiagnosticBuilder, DiagnosticId}; use require_c_abi_if_variadic; use session::{CompileIncomplete, config, Session}; @@ -1104,6 +1105,7 @@ fn check_struct<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, check_simd(tcx, span, def_id); } + check_transparent(tcx, span, def_id); check_packed(tcx, span, def_id); } @@ -1517,6 +1519,42 @@ fn check_packed_inner<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, false } +fn check_transparent<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, sp: Span, def_id: DefId) { + let adt = tcx.adt_def(def_id); + if !adt.repr.transparent() { + return; + } + + // For each field, figure out if it's known to be a ZST and align(1) + let field_infos: Vec<_> = adt.non_enum_variant().fields.iter().map(|field| { + let ty = field.ty(tcx, Substs::identity_for_item(tcx, field.did)); + let param_env = tcx.param_env(field.did); + let layout = (tcx, param_env).layout_of(ty); + // We are currently checking the type this field came from, so it must be local + let span = tcx.hir.span_if_local(field.did).unwrap(); + let zst = layout.map(|layout| layout.is_zst()).unwrap_or(false); + let align1 = layout.map(|layout| layout.align.abi() == 1).unwrap_or(false); + (span, zst, align1) + }).collect(); + + let non_zst_fields = field_infos.iter().filter(|(_span, zst, _align1)| !*zst); + let non_zst_count = non_zst_fields.clone().count(); + if non_zst_count != 1 { + let field_spans: Vec<_> = non_zst_fields.map(|(span, _zst, _align1)| *span).collect(); + struct_span_err!(tcx.sess, sp, E0690, + "transparent struct needs exactly one non-zero-sized field, but has {}", + non_zst_count) + .span_note(field_spans, "non-zero-sized field") + .emit(); + } + for &(span, zst, align1) in &field_infos { + if zst && !align1 { + span_err!(tcx.sess, span, E0691, + "zero-sized field in transparent struct has alignment larger than 1"); + } + } +} + #[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 1913b940c08fe..4f661a4f5e67d 100644 --- a/src/librustc_typeck/diagnostics.rs +++ b/src/librustc_typeck/diagnostics.rs @@ -4605,6 +4605,99 @@ let _ = x.powi(2); let _ = (2.0 as f32).powi(2); ``` "##, + +E0690: r##" +A struct with the representation hint `repr(transparent)` had zero or more than +on fields that were not guaranteed to be zero-sized. + +Erroneous code example: + +```compile_fail,E0690 +#![feature(repr_transparent)] + +#[repr(transparent)] +struct LengthWithUnit { // error: transparent struct needs exactly one + value: f32, // non-zero-sized field, but has 2 + unit: U, +} +``` + +Because transparent structs are represented exactly like one of their fields at +run time, said field must be uniquely determined. If there is no field, or if +there are multiple fields, it is not clear how the struct should be represented. +Note that fields of zero-typed types (e.g., `PhantomData`) can also exist +alongside the field that contains the actual data, they do not count for this +error. When generic types are involved (as in the above example), an error is +reported because the type parameter could be non-zero-sized. + +To combine `repr(transparent)` with type parameters, `PhantomData` may be +useful: + +``` +#![feature(repr_transparent)] + +use std::marker::PhantomData; + +#[repr(transparent)] +struct LengthWithUnit { + value: f32, + unit: PhantomData, +} +``` +"##, + +E0691: r##" +A struct with the `repr(transparent)` representation hint contains a zero-sized +field that requires non-trivial alignment. + +Erroneous code example: + +```compile_fail,E0691 +#![feature(repr_transparent, repr_align, attr_literals)] + +#[repr(align(32))] +struct ForceAlign32; + +#[repr(transparent)] +struct Wrapper(f32, ForceAlign32); // error: zero-sized field in transparent + // struct has alignment larger than 1 +``` + +A transparent struct is supposed to be represented exactly like the piece of +data it contains. Zero-sized fields with different alignment requirements +potentially conflict with this property. In the example above, `Wrapper` would +have to be aligned to 32 bytes even though `f32` has a smaller alignment +requirement. + +Consider removing the over-aligned zero-sized field: + +``` +#![feature(repr_transparent)] + +#[repr(transparent)] +struct Wrapper(f32); +``` + +Alternatively, `PhantomData` has alignment 1 for all `T`, so you can use it +if you need to keep the field for some reason: + +``` +#![feature(repr_transparent, repr_align, attr_literals)] + +use std::marker::PhantomData; + +#[repr(align(32))] +struct ForceAlign32; + +#[repr(transparent)] +struct Wrapper(f32, PhantomData); +``` + +Note that empty arrays `[T; 0]` have the same alignment requirement as the +element type `T`. Also note that the error is conservatively reported even when +the alignment of the zero-sized type is less than or equal to the data field's +alignment. +"##, } register_diagnostics! { diff --git a/src/librustc_typeck/lib.rs b/src/librustc_typeck/lib.rs index 1dc98bc7a0072..786a678344064 100644 --- a/src/librustc_typeck/lib.rs +++ b/src/librustc_typeck/lib.rs @@ -77,6 +77,7 @@ This API is completely unstable and subject to change. #![feature(box_syntax)] #![feature(crate_visibility_modifier)] #![feature(conservative_impl_trait)] +#![feature(copy_closures, clone_closures)] #![feature(from_ref)] #![feature(match_default_bindings)] #![feature(never_type)] diff --git a/src/libsyntax/attr.rs b/src/libsyntax/attr.rs index 4291f811f3f74..d18d6f5e6bd0d 100644 --- a/src/libsyntax/attr.rs +++ b/src/libsyntax/attr.rs @@ -992,7 +992,8 @@ pub fn find_deprecation(diagnostic: &Handler, attrs: &[Attribute], /// Valid repr contents: any of the primitive integral type names (see /// `int_type_of_word`, below) to specify enum discriminant type; `C`, to use /// the same discriminant size that the corresponding C enum would or C -/// structure layout, and `packed` to remove padding. +/// structure layout, `packed` to remove padding, and `transparent` to elegate representation +/// concerns to the only non-ZST field. pub fn find_repr_attrs(diagnostic: &Handler, attr: &Attribute) -> Vec { let mut acc = Vec::new(); if attr.path == "repr" { @@ -1011,6 +1012,7 @@ pub fn find_repr_attrs(diagnostic: &Handler, attr: &Attribute) -> Vec "C" => Some(ReprC), "packed" => Some(ReprPacked), "simd" => Some(ReprSimd), + "transparent" => Some(ReprTransparent), _ => match int_type_of_word(word) { Some(ity) => Some(ReprInt(ity)), None => { @@ -1082,6 +1084,7 @@ pub enum ReprAttr { ReprC, ReprPacked, ReprSimd, + ReprTransparent, ReprAlign(u32), } diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 196fadcc997f5..953b2deb87790 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -452,6 +452,9 @@ declare_features! ( // `extern` in paths (active, extern_in_paths, "1.23.0", Some(44660)), + + // Allows `#[repr(transparent)]` attribute on newtype structs + (active, repr_transparent, "1.25.0", Some(43036)), ); declare_features! ( @@ -1524,6 +1527,11 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> { "the struct `#[repr(align(u16))]` attribute \ is experimental"); } + if item.check_name("transparent") { + gate_feature_post!(&self, repr_transparent, attr.span, + "the `#[repr(transparent)]` attribute \ + is experimental"); + } } } } diff --git a/src/libsyntax_ext/deriving/generic/mod.rs b/src/libsyntax_ext/deriving/generic/mod.rs index 08a7f85d14b2f..3660d2fe46a92 100644 --- a/src/libsyntax_ext/deriving/generic/mod.rs +++ b/src/libsyntax_ext/deriving/generic/mod.rs @@ -830,7 +830,9 @@ 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 | attr::ReprAlign(_) => continue, + attr::ReprPacked | attr::ReprSimd | attr::ReprAlign(_) | attr::ReprTransparent => + continue, + attr::ReprC => "i32", attr::ReprInt(attr::SignedInt(ast::IntTy::Isize)) => "isize", diff --git a/src/test/codegen/repr-transparent-aggregates-1.rs b/src/test/codegen/repr-transparent-aggregates-1.rs new file mode 100644 index 0000000000000..2eeed2b788ce2 --- /dev/null +++ b/src/test/codegen/repr-transparent-aggregates-1.rs @@ -0,0 +1,53 @@ +// 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. + +// compile-flags: -C no-prepopulate-passes + +// ignore-arm +// ignore-mips +// ignore-mips64 +// ignore-powerpc +// ignore-powerpc64 +// See repr-transparent.rs + +#![crate_type="lib"] +#![feature(repr_transparent)] + + +#[repr(C)] +pub struct Big([u32; 16]); + +#[repr(transparent)] +pub struct BigW(Big); + +// CHECK: define void @test_Big(%Big* [[BIG_RET_ATTRS:.*]], %Big* [[BIG_ARG_ATTRS:.*]]) +#[no_mangle] +pub extern fn test_Big(_: Big) -> Big { loop {} } + +// CHECK: define void @test_BigW(%BigW* [[BIG_RET_ATTRS]], %BigW* [[BIG_ARG_ATTRS]]) +#[no_mangle] +pub extern fn test_BigW(_: BigW) -> BigW { loop {} } + + +#[repr(C)] +pub union BigU { + foo: [u32; 16], +} + +#[repr(transparent)] +pub struct BigUw(BigU); + +// CHECK: define void @test_BigU(%BigU* [[BIGU_RET_ATTRS:.*]], %BigU* [[BIGU_ARG_ATTRS:.*]]) +#[no_mangle] +pub extern fn test_BigU(_: BigU) -> BigU { loop {} } + +// CHECK: define void @test_BigUw(%BigUw* [[BIGU_RET_ATTRS]], %BigUw* [[BIGU_ARG_ATTRS]]) +#[no_mangle] +pub extern fn test_BigUw(_: BigUw) -> BigUw { loop {} } diff --git a/src/test/codegen/repr-transparent-aggregates-2.rs b/src/test/codegen/repr-transparent-aggregates-2.rs new file mode 100644 index 0000000000000..e6374928a5cbf --- /dev/null +++ b/src/test/codegen/repr-transparent-aggregates-2.rs @@ -0,0 +1,54 @@ +// 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. + +// compile-flags: -C no-prepopulate-passes + +// ignore-aarch64 +// ignore-asmjs +// ignore-s390x +// ignore-wasm +// ignore-x86 +// ignore-x86_64 +// See repr-transparent.rs + +#![crate_type="lib"] +#![feature(repr_transparent)] + + +#[repr(C)] +pub struct Big([u32; 16]); + +#[repr(transparent)] +pub struct BigW(Big); + +// CHECK: define void @test_Big(%Big* [[BIG_RET_ATTRS:.*]], [16 x i32] +#[no_mangle] +pub extern fn test_Big(_: Big) -> Big { loop {} } + +// CHECK: define void @test_BigW(%BigW* [[BIG_RET_ATTRS]], [16 x i32] +#[no_mangle] +pub extern fn test_BigW(_: BigW) -> BigW { loop {} } + + +#[repr(C)] +pub union BigU { + foo: [u32; 16], +} + +#[repr(transparent)] +pub struct BigUw(BigU); + +// CHECK: define void @test_BigU(%BigU* [[BIGU_RET_ATTRS:.*]], [16 x i32] +#[no_mangle] +pub extern fn test_BigU(_: BigU) -> BigU { loop {} } + +// CHECK: define void @test_BigUw(%BigUw* [[BIGU_RET_ATTRS]], [16 x i32] +#[no_mangle] +pub extern fn test_BigUw(_: BigUw) -> BigUw { loop {} } diff --git a/src/test/codegen/repr-transparent.rs b/src/test/codegen/repr-transparent.rs new file mode 100644 index 0000000000000..31020d8b94f80 --- /dev/null +++ b/src/test/codegen/repr-transparent.rs @@ -0,0 +1,177 @@ +// 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. + +// compile-flags: -C no-prepopulate-passes + +#![crate_type="lib"] +#![feature(repr_transparent, repr_simd)] + +use std::marker::PhantomData; + +pub struct Zst1; +pub struct Zst2(()); + +#[repr(transparent)] +pub struct F32(f32); + +// CHECK: define float @test_F32(float %arg0) +#[no_mangle] +pub extern fn test_F32(_: F32) -> F32 { loop {} } + +#[repr(transparent)] +pub struct Ptr(*mut u8); + +// CHECK: define i8* @test_Ptr(i8* %arg0) +#[no_mangle] +pub extern fn test_Ptr(_: Ptr) -> Ptr { loop {} } + +#[repr(transparent)] +pub struct WithZst(u64, Zst1); + +// CHECK: define i64 @test_WithZst(i64 %arg0) +#[no_mangle] +pub extern fn test_WithZst(_: WithZst) -> WithZst { loop {} } + +#[repr(transparent)] +pub struct WithZeroSizedArray(*const f32, [i8; 0]); + +// Apparently we use i32* when newtype-unwrapping f32 pointers. Whatever. +// CHECK: define i32* @test_WithZeroSizedArray(i32* %arg0) +#[no_mangle] +pub extern fn test_WithZeroSizedArray(_: WithZeroSizedArray) -> WithZeroSizedArray { loop {} } + +#[repr(transparent)] +pub struct Generic(T); + +// CHECK: define double @test_Generic(double %arg0) +#[no_mangle] +pub extern fn test_Generic(_: Generic) -> Generic { loop {} } + +#[repr(transparent)] +pub struct GenericPlusZst(T, Zst2); + +#[repr(u8)] +pub enum Bool { True, False, FileNotFound } + +// CHECK: define{{( zeroext)?}} i8 @test_Gpz(i8{{( zeroext)?}} %arg0) +#[no_mangle] +pub extern fn test_Gpz(_: GenericPlusZst) -> GenericPlusZst { loop {} } + +#[repr(transparent)] +pub struct LifetimePhantom<'a, T: 'a>(*const T, PhantomData<&'a T>); + +// CHECK: define i16* @test_LifetimePhantom(i16* %arg0) +#[no_mangle] +pub extern fn test_LifetimePhantom(_: LifetimePhantom) -> LifetimePhantom { loop {} } + +// This works despite current alignment resrictions because PhantomData is always align(1) +#[repr(transparent)] +pub struct UnitPhantom { val: T, unit: PhantomData } + +pub struct Px; + +// CHECK: define float @test_UnitPhantom(float %arg0) +#[no_mangle] +pub extern fn test_UnitPhantom(_: UnitPhantom) -> UnitPhantom { loop {} } + +#[repr(transparent)] +pub struct TwoZsts(Zst1, i8, Zst2); + +// CHECK: define{{( signext)?}} i8 @test_TwoZsts(i8{{( signext)?}} %arg0) +#[no_mangle] +pub extern fn test_TwoZsts(_: TwoZsts) -> TwoZsts { loop {} } + +#[repr(transparent)] +pub struct Nested1(Zst2, Generic); + +// CHECK: define double @test_Nested1(double %arg0) +#[no_mangle] +pub extern fn test_Nested1(_: Nested1) -> Nested1 { loop {} } + +#[repr(transparent)] +pub struct Nested2(Nested1, Zst1); + +// CHECK: define double @test_Nested2(double %arg0) +#[no_mangle] +pub extern fn test_Nested2(_: Nested2) -> Nested2 { loop {} } + +#[repr(simd)] +struct f32x4(f32, f32, f32, f32); + +#[repr(transparent)] +pub struct Vector(f32x4); + +// CHECK: define <4 x float> @test_Vector(<4 x float> %arg0) +#[no_mangle] +pub extern fn test_Vector(_: Vector) -> Vector { loop {} } + +trait Mirror { type It: ?Sized; } +impl Mirror for T { type It = Self; } + +#[repr(transparent)] +pub struct StructWithProjection(::It); + +// CHECK: define float @test_Projection(float %arg0) +#[no_mangle] +pub extern fn test_Projection(_: StructWithProjection) -> StructWithProjection { loop {} } + + +// The rest of this file tests newtypes around small aggregates on an ABI where small aggregates are +// packed into one register. This is ABI-dependent, so instead we focus on one ABI and supply a +// dummy definition for other ABIs to keep FileCheck happy. +// +// Bigger aggregates are tested in separate files called repr-transparent-aggregate-*.rs because +// there, the expected LLVM IR function signatures vary so much that it's not reasonably possible to +// cover all of them with a single CHECK line. Instead we group ABIs by the general "shape" of the +// signature and have a separate test file for each bin. +// +// PS: You may be wondering why we don't just compare the return types and argument types for +// equality with FileCheck regex captures. Well, rustc doesn't perform newtype unwrapping on +// newtypes containing aggregates. This is OK on all ABIs we support, but because LLVM has not +// gotten rid of pointee types yet, the IR function signature will be syntactically different (%Foo* +// vs %FooWrapper*). + +#[repr(C)] +pub struct Rgb8 { r: u8, g: u8, b: u8 } + +#[repr(transparent)] +pub struct Rgb8Wrap(Rgb8); + +// NB: closing parenthesis is missing because sometimes the argument has a name and sometimes not +// CHECK: define i32 @test_Rgb8Wrap(i32 +#[no_mangle] +#[cfg(all(target_arch="x86_64", target_os="linux"))] +pub extern fn test_Rgb8Wrap(_: Rgb8Wrap) -> Rgb8Wrap { loop {} } + +#[cfg(not(all(target_arch="x86_64", target_os="linux")))] +#[no_mangle] +pub extern fn test_Rgb8Wrap(_: u32) -> u32 { loop {} } + +// Same as with the small struct above: ABI-dependent, we only test the interesting case +// (ABIs that pack the aggregate into a scalar) and stub it out on other ABIs + +#[repr(C)] +pub union FloatBits { + float: f32, + bits: u32, +} + +#[repr(transparent)] +pub struct SmallUnion(FloatBits); + +// NB: closing parenthesis is missing because sometimes the argument has a name and sometimes not +// CHECK: define i32 @test_SmallUnion(i32 +#[no_mangle] +#[cfg(all(target_arch="x86_64", target_os="linux"))] +pub extern fn test_SmallUnion(_: SmallUnion) -> SmallUnion { loop {} } + +#[cfg(not(all(target_arch="x86_64", target_os="linux")))] +#[no_mangle] +pub extern fn test_SmallUnion(_: u32) -> u32 { loop {} } diff --git a/src/test/compile-fail/lint-ctypes.rs b/src/test/compile-fail/lint-ctypes.rs index fd825563eba19..c22239dee0a80 100644 --- a/src/test/compile-fail/lint-ctypes.rs +++ b/src/test/compile-fail/lint-ctypes.rs @@ -9,10 +9,12 @@ // except according to those terms. #![deny(improper_ctypes)] -#![feature(libc, i128_type)] +#![feature(libc, i128_type, repr_transparent)] extern crate libc; +use std::marker::PhantomData; + trait Mirror { type It: ?Sized; } impl Mirror for T { type It = Self; } #[repr(C)] @@ -28,6 +30,22 @@ pub type RustFn = fn(); pub type RustBadRet = extern fn() -> Box; pub type CVoidRet = (); pub struct Foo; +#[repr(transparent)] +pub struct TransparentI128(i128); +#[repr(transparent)] +pub struct TransparentStr(&'static str); +#[repr(transparent)] +pub struct TransparentBadFn(RustBadRet); +#[repr(transparent)] +pub struct TransparentInt(u32); +#[repr(transparent)] +pub struct TransparentRef<'a>(&'a TransparentInt); +#[repr(transparent)] +pub struct TransparentLifetime<'a>(*const u8, PhantomData<&'a ()>); +#[repr(transparent)] +pub struct TransparentUnit(f32, PhantomData); +#[repr(transparent)] +pub struct TransparentCustomZst(i32, ZeroSize); #[repr(C)] pub struct ZeroSizeWithPhantomData(::std::marker::PhantomData); @@ -51,6 +69,9 @@ extern { pub fn fn_type(p: RustFn); //~ ERROR found function pointer with Rust pub fn fn_type2(p: fn()); //~ ERROR found function pointer with Rust pub fn fn_contained(p: RustBadRet); //~ ERROR: found struct without + pub fn transparent_i128(p: TransparentI128); //~ ERROR: found Rust type `i128` + pub fn transparent_str(p: TransparentStr); //~ ERROR: found Rust type `str` + pub fn transparent_fn(p: TransparentBadFn); //~ ERROR: found struct without pub fn good3(fptr: Option); pub fn good4(aptr: &[u8; 4 as usize]); @@ -62,6 +83,11 @@ extern { pub fn good10() -> CVoidRet; pub fn good11(size: isize); pub fn good12(size: usize); + pub fn good13(n: TransparentInt); + pub fn good14(p: TransparentRef); + pub fn good15(p: TransparentLifetime); + pub fn good16(p: TransparentUnit); + pub fn good17(p: TransparentCustomZst); } #[cfg(not(target_arch = "wasm32"))] diff --git a/src/test/compile-fail/repr-transparent-other-items.rs b/src/test/compile-fail/repr-transparent-other-items.rs new file mode 100644 index 0000000000000..cf0870866c76d --- /dev/null +++ b/src/test/compile-fail/repr-transparent-other-items.rs @@ -0,0 +1,40 @@ +// 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(repr_transparent)] + +// See also repr-transparent.rs + +#[repr(transparent)] //~ ERROR unsupported representation for zero-variant enum +enum Void {} //~| ERROR should be applied to struct + +#[repr(transparent)] //~ ERROR should be applied to struct +enum FieldlessEnum { + Foo, + Bar, +} + +#[repr(transparent)] //~ ERROR should be applied to struct +enum Enum { + Foo(String), + Bar(u32), +} + +#[repr(transparent)] //~ ERROR should be applied to struct +union Foo { + u: u32, + s: i32 +} + +#[repr(transparent)] //~ ERROR should be applied to struct +fn cant_repr_this() {} + +#[repr(transparent)] //~ ERROR should be applied to struct +static CANT_REPR_THIS: u32 = 0; diff --git a/src/test/compile-fail/repr-transparent-other-reprs.rs b/src/test/compile-fail/repr-transparent-other-reprs.rs new file mode 100644 index 0000000000000..7b91a6f68e3d5 --- /dev/null +++ b/src/test/compile-fail/repr-transparent-other-reprs.rs @@ -0,0 +1,28 @@ +// 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(repr_transparent, repr_align, attr_literals)] + +// See also repr-transparent.rs + +#[repr(transparent, C)] //~ ERROR cannot have other repr +struct TransparentPlusC { + ptr: *const u8 +} + +#[repr(transparent, packed)] //~ ERROR cannot have other repr +struct TransparentPlusPacked(*const u8); + +#[repr(transparent, align(2))] //~ ERROR cannot have other repr +struct TransparentPlusAlign(u8); + +#[repr(transparent)] //~ ERROR cannot have other repr +#[repr(C)] +struct SeparateAttributes(*mut u8); diff --git a/src/test/compile-fail/repr-transparent.rs b/src/test/compile-fail/repr-transparent.rs new file mode 100644 index 0000000000000..b5e6a0fa0b137 --- /dev/null +++ b/src/test/compile-fail/repr-transparent.rs @@ -0,0 +1,51 @@ +// 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. + +// This file tests repr(transparent)-related errors reported during typeck. Other errors +// that are reported earlier and therefore preempt these are tested in: +// - repr-transparent-other-reprs.rs +// - repr-transparent-other-items.rs + +#![feature(repr_align, attr_literals)] +#![feature(repr_transparent)] + +use std::marker::PhantomData; + +#[repr(transparent)] +struct NoFields; //~ ERROR needs exactly one non-zero-sized field + +#[repr(transparent)] +struct ContainsOnlyZst(()); //~ ERROR needs exactly one non-zero-sized field + +#[repr(transparent)] +struct ContainsOnlyZstArray([bool; 0]); //~ ERROR needs exactly one non-zero-sized field + +#[repr(transparent)] +struct ContainsMultipleZst(PhantomData<*const i32>, NoFields); +//~^ ERROR needs exactly one non-zero-sized field + +#[repr(transparent)] +struct MultipleNonZst(u8, u8); //~ ERROR needs exactly one non-zero-sized field + +trait Mirror { type It: ?Sized; } +impl Mirror for T { type It = Self; } + +#[repr(transparent)] +pub struct StructWithProjection(f32, ::It); +//~^ ERROR needs exactly one non-zero-sized field + +#[repr(transparent)] +struct NontrivialAlignZst(u32, [u16; 0]); //~ ERROR alignment larger than 1 + +#[repr(align(32))] +struct ZstAlign32(PhantomData); + +#[repr(transparent)] +struct GenericAlign(ZstAlign32, u32); //~ ERROR alignment larger than 1 diff --git a/src/test/ui/feature-gate-repr_transparent.rs b/src/test/ui/feature-gate-repr_transparent.rs new file mode 100644 index 0000000000000..deadf2e535d05 --- /dev/null +++ b/src/test/ui/feature-gate-repr_transparent.rs @@ -0,0 +1,14 @@ +// 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. + +#[repr(transparent)] //~ error: the `#[repr(transparent)]` attribute is experimental +struct Foo(u64); + +fn main() {} diff --git a/src/test/ui/feature-gate-repr_transparent.stderr b/src/test/ui/feature-gate-repr_transparent.stderr new file mode 100644 index 0000000000000..d1292e95491ab --- /dev/null +++ b/src/test/ui/feature-gate-repr_transparent.stderr @@ -0,0 +1,10 @@ +error[E0658]: the `#[repr(transparent)]` attribute is experimental (see issue #43036) + --> $DIR/feature-gate-repr_transparent.rs:11:1 + | +11 | #[repr(transparent)] //~ error: the `#[repr(transparent)]` attribute is experimental + | ^^^^^^^^^^^^^^^^^^^^ + | + = help: add #![feature(repr_transparent)] to the crate attributes to enable + +error: aborting due to previous error +